mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 07:16:09 +03:00
Update AuthUser API (#1915)
This commit is contained in:
parent
c3e4a10d1b
commit
0e70547f7b
@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## 0.14.0 (2024-04-22)
|
||||
|
||||
### 🎉 New Features
|
||||
|
||||
- Simplified Auth User API: Introduced a simpler API for accessing user auth fields (for example `username`, `email`, `isEmailVerified`) directly on the `user` object, eliminating the need for helper functions.
|
||||
|
||||
### ⚠️ Breaking Changes & Migration Guide
|
||||
|
||||
We had to make a couple of breaking changes to reach the new simpler API:
|
||||
|
||||
1. You don't need to use `getUsername` to access the username:
|
||||
|
||||
- Before: Used `getUsername` to access the username.
|
||||
- After: Directly use `user.identities.username?.id`.
|
||||
|
||||
2. You don't need to use `getEmail` to access the email:
|
||||
|
||||
- Before: Used `getEmail` to access the email.
|
||||
- After: Directly use `user.identities.email?.id`.
|
||||
|
||||
3. Better API for accessing `providerData`:
|
||||
|
||||
- Before: Required complex logic to access typed provider data.
|
||||
- After: Directly use `user.identities.<provider>.<value>` for typed access.
|
||||
|
||||
4. Better API for accessing `getFirstProviderUserId`:
|
||||
|
||||
- Before: Used `getFirstProviderUserId(user)` to get the ID.
|
||||
- After: Use `user.getFirstProviderUserId()` directly on the user object.
|
||||
|
||||
5. You don't need to use `findUserIdentity` any more:
|
||||
|
||||
- Before: Relied on `findUserIdentity` to check which user identity exists.
|
||||
- After: Directly check `user.identities.<provider>` existence.
|
||||
|
||||
These changes improve code readability and lower the complexity of accessing user's auth fields. Follow the [detailed migration steps to update your project to 0.14.0](https://wasp-lang.dev/docs/migrate-from-0-13-to-0-14).
|
||||
|
||||
### Note on Auth Helper Functions (`getUsername`, `getEmail` etc.)
|
||||
|
||||
These changes only apply to getting auth fields from the `user` object you receive from Wasp, for example in the `authRequired` enabled pages or `context.user` on the server. If you are fetching the user and auth fields with your own queries, you _can_ keep using most of the helpers. Read more [about using the auth helpers](https://wasp-lang.dev/docs/auth/entities#including-the-user-with-other-entities).
|
||||
|
||||
## 0.13.2 (2024-04-11)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
@ -27,12 +27,12 @@ import { SocialButton } from '../social/SocialButton'
|
||||
{=# isAnyPasswordBasedAuthEnabled =}
|
||||
import { useHistory } from 'react-router-dom'
|
||||
{=/ isAnyPasswordBasedAuthEnabled =}
|
||||
{=# isUsernameAndPasswordAuthEnabled =}
|
||||
{=# enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
import { useUsernameAndPassword } from '../usernameAndPassword/useUsernameAndPassword'
|
||||
{=/ isUsernameAndPasswordAuthEnabled =}
|
||||
{=# isEmailAuthEnabled =}
|
||||
{=/ enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
{=# enabledProviders.isEmailAuthEnabled =}
|
||||
import { useEmail } from '../email/useEmail'
|
||||
{=/ isEmailAuthEnabled =}
|
||||
{=/ enabledProviders.isEmailAuthEnabled =}
|
||||
|
||||
{=# areBothSocialAndPasswordBasedAuthEnabled =}
|
||||
const OrContinueWith = styled('div', {
|
||||
@ -105,15 +105,15 @@ const SocialAuthButtons = styled('div', {
|
||||
}
|
||||
})
|
||||
{=/ isSocialAuthEnabled =}
|
||||
{=# isGoogleAuthEnabled =}
|
||||
{=# enabledProviders.isGoogleAuthEnabled =}
|
||||
const googleSignInUrl = `${config.apiUrl}{= googleSignInPath =}`
|
||||
{=/ isGoogleAuthEnabled =}
|
||||
{=# isKeycloakAuthEnabled =}
|
||||
{=/ enabledProviders.isGoogleAuthEnabled =}
|
||||
{=# enabledProviders.isKeycloakAuthEnabled =}
|
||||
const keycloakSignInUrl = `${config.apiUrl}{= keycloakSignInPath =}`
|
||||
{=/ isKeycloakAuthEnabled =}
|
||||
{=# isGitHubAuthEnabled =}
|
||||
{=/ enabledProviders.isKeycloakAuthEnabled =}
|
||||
{=# enabledProviders.isGitHubAuthEnabled =}
|
||||
const gitHubSignInUrl = `${config.apiUrl}{= gitHubSignInPath =}`
|
||||
{=/ isGitHubAuthEnabled =}
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
|
||||
{=!
|
||||
// Since we allow users to add additional fields to the signup form, we don't
|
||||
@ -151,7 +151,7 @@ export const LoginSignupForm = ({
|
||||
{=/ isAnyPasswordBasedAuthEnabled =}
|
||||
const hookForm = useForm<LoginSignupFormFields>()
|
||||
const { register, formState: { errors }, handleSubmit: hookFormHandleSubmit } = hookForm
|
||||
{=# isUsernameAndPasswordAuthEnabled =}
|
||||
{=# enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
const { handleSubmit } = useUsernameAndPassword({
|
||||
isLogin,
|
||||
onError: onErrorHandler,
|
||||
@ -159,8 +159,8 @@ export const LoginSignupForm = ({
|
||||
history.push('{= onAuthSucceededRedirectTo =}')
|
||||
},
|
||||
});
|
||||
{=/ isUsernameAndPasswordAuthEnabled =}
|
||||
{=# isEmailAuthEnabled =}
|
||||
{=/ enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
{=# enabledProviders.isEmailAuthEnabled =}
|
||||
const { handleSubmit } = useEmail({
|
||||
isLogin,
|
||||
onError: onErrorHandler,
|
||||
@ -172,7 +172,7 @@ export const LoginSignupForm = ({
|
||||
history.push('{= onAuthSucceededRedirectTo =}')
|
||||
},
|
||||
});
|
||||
{=/ isEmailAuthEnabled =}
|
||||
{=/ enabledProviders.isEmailAuthEnabled =}
|
||||
{=# isAnyPasswordBasedAuthEnabled =}
|
||||
async function onSubmit (data) {
|
||||
setIsLoading(true);
|
||||
@ -191,17 +191,17 @@ export const LoginSignupForm = ({
|
||||
<SocialAuth>
|
||||
<SocialAuthLabel>{cta} with</SocialAuthLabel>
|
||||
<SocialAuthButtons gap='large' direction={socialButtonsDirection}>
|
||||
{=# isGoogleAuthEnabled =}
|
||||
{=# enabledProviders.isGoogleAuthEnabled =}
|
||||
<SocialButton href={googleSignInUrl}><SocialIcons.Google/></SocialButton>
|
||||
{=/ isGoogleAuthEnabled =}
|
||||
{=/ enabledProviders.isGoogleAuthEnabled =}
|
||||
|
||||
{=# isKeycloakAuthEnabled =}
|
||||
{=# enabledProviders.isKeycloakAuthEnabled =}
|
||||
<SocialButton href={keycloakSignInUrl}><SocialIcons.Keycloak/></SocialButton>
|
||||
{=/ isKeycloakAuthEnabled =}
|
||||
{=/ enabledProviders.isKeycloakAuthEnabled =}
|
||||
|
||||
{=# isGitHubAuthEnabled =}
|
||||
{=# enabledProviders.isGitHubAuthEnabled =}
|
||||
<SocialButton href={gitHubSignInUrl}><SocialIcons.GitHub/></SocialButton>
|
||||
{=/ isGitHubAuthEnabled =}
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
</SocialAuthButtons>
|
||||
</SocialAuth>
|
||||
{=/ isSocialAuthEnabled =}
|
||||
@ -217,7 +217,7 @@ export const LoginSignupForm = ({
|
||||
{=/ areBothSocialAndPasswordBasedAuthEnabled =}
|
||||
{=# isAnyPasswordBasedAuthEnabled =}
|
||||
<Form onSubmit={hookFormHandleSubmit(onSubmit)}>
|
||||
{=# isUsernameAndPasswordAuthEnabled =}
|
||||
{=# enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
<FormItemGroup>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormInput
|
||||
@ -229,8 +229,8 @@ export const LoginSignupForm = ({
|
||||
/>
|
||||
{errors.username && <FormError>{errors.username.message}</FormError>}
|
||||
</FormItemGroup>
|
||||
{=/ isUsernameAndPasswordAuthEnabled =}
|
||||
{=# isEmailAuthEnabled =}
|
||||
{=/ enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
{=# enabledProviders.isEmailAuthEnabled =}
|
||||
<FormItemGroup>
|
||||
<FormLabel>E-mail</FormLabel>
|
||||
<FormInput
|
||||
@ -242,7 +242,7 @@ export const LoginSignupForm = ({
|
||||
/>
|
||||
{errors.email && <FormError>{errors.email.message}</FormError>}
|
||||
</FormItemGroup>
|
||||
{=/ isEmailAuthEnabled =}
|
||||
{=/ enabledProviders.isEmailAuthEnabled =}
|
||||
<FormItemGroup>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormInput
|
||||
|
@ -1,4 +1,11 @@
|
||||
// PUBLIC
|
||||
export type { AuthUser } from '../server/_types'
|
||||
// PUBLIC API
|
||||
export {
|
||||
getEmail,
|
||||
getUsername,
|
||||
getFirstProviderUserId,
|
||||
} from './user.js'
|
||||
|
||||
export { getEmail, getUsername, getFirstProviderUserId, findUserIdentity } from './user.js'
|
||||
// PUBLIC API
|
||||
export {
|
||||
type AuthUser,
|
||||
} from '../server/auth/user.js';
|
||||
|
@ -6,12 +6,10 @@ import { type AuthUser } from 'wasp/auth'
|
||||
|
||||
import { auth } from "./lucia.js";
|
||||
import type { Session } from "lucia";
|
||||
import {
|
||||
throwInvalidCredentialsError,
|
||||
deserializeAndSanitizeProviderData,
|
||||
} from "./utils.js";
|
||||
import { throwInvalidCredentialsError } from "./utils.js";
|
||||
|
||||
import { prisma } from 'wasp/server';
|
||||
import { createAuthUser } from "../server/auth/user.js";
|
||||
|
||||
// PRIVATE API
|
||||
// Creates a new session for the `authId` in the database
|
||||
@ -81,29 +79,7 @@ async function getUser(userId: {= userEntityUpper =}['id']): Promise<AuthUser> {
|
||||
throwInvalidCredentialsError()
|
||||
}
|
||||
|
||||
// TODO: This logic must match the type in _types/index.ts (if we remove the
|
||||
// password field from the object here, we must to do the same there).
|
||||
// Ideally, these two things would live in the same place:
|
||||
// https://github.com/wasp-lang/wasp/issues/965
|
||||
const deserializedIdentities = user.{= authFieldOnUserEntityName =}.{= identitiesFieldOnAuthEntityName =}.map((identity) => {
|
||||
const deserializedProviderData = deserializeAndSanitizeProviderData(
|
||||
identity.providerData,
|
||||
{
|
||||
shouldRemovePasswordField: true,
|
||||
}
|
||||
)
|
||||
return {
|
||||
...identity,
|
||||
providerData: deserializedProviderData,
|
||||
}
|
||||
})
|
||||
return {
|
||||
...user,
|
||||
auth: {
|
||||
...user.auth,
|
||||
identities: deserializedIdentities,
|
||||
},
|
||||
}
|
||||
return createAuthUser(user);
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
|
@ -1,2 +1,2 @@
|
||||
// todo(filip): turn into a proper import/path
|
||||
export type { AuthUser, ProviderName, DeserializedAuthIdentity } from 'wasp/server/_types'
|
||||
export type { ProviderName } from 'wasp/server/_types'
|
||||
|
@ -3,7 +3,7 @@ import { deserialize as superjsonDeserialize } from 'superjson'
|
||||
import { useQuery, addMetadataToQuery } from 'wasp/client/operations'
|
||||
import { api, handleApiError } from 'wasp/client/api'
|
||||
import { HttpMethod } from 'wasp/client'
|
||||
import type { AuthUser } from './types'
|
||||
import type { AuthUser } from '../server/auth/user.js'
|
||||
import { UseQueryResult } from '@tanstack/react-query'
|
||||
|
||||
// PUBLIC API
|
||||
|
@ -1,17 +1,24 @@
|
||||
import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types'
|
||||
{{={= =}=}}
|
||||
import { type {= authIdentityEntityName =} } from '../entities/index.js'
|
||||
import { type ProviderName } from '../server/_types/index.js'
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
*/
|
||||
import { type UserEntityWithAuth } from '../server/auth/user.js'
|
||||
|
||||
// PUBLIC API
|
||||
export function getEmail(user: AuthUser): string | null {
|
||||
export function getEmail(user: UserEntityWithAuth): string | null {
|
||||
return findUserIdentity(user, "email")?.providerUserId ?? null;
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function getUsername(user: AuthUser): string | null {
|
||||
export function getUsername(user: UserEntityWithAuth): string | null {
|
||||
return findUserIdentity(user, "username")?.providerUserId ?? null;
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function getFirstProviderUserId(user?: AuthUser): string | null {
|
||||
export function getFirstProviderUserId(user?: UserEntityWithAuth): string | null {
|
||||
if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@ -19,9 +26,11 @@ export function getFirstProviderUserId(user?: AuthUser): string | null {
|
||||
return user.auth.identities[0].providerUserId ?? null;
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined {
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): {= authIdentityEntityName =} | null {
|
||||
if (!user.auth) {
|
||||
return null;
|
||||
}
|
||||
return user.auth.identities.find(
|
||||
(identity) => identity.providerName === providerName
|
||||
);
|
||||
) ?? null;
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } fr
|
||||
import { prisma } from 'wasp/server'
|
||||
{=# isAuthEnabled =}
|
||||
import {
|
||||
type {= userEntityName =},
|
||||
type {= authEntityName =},
|
||||
type {= authIdentityEntityName =},
|
||||
} from "wasp/entities"
|
||||
import {
|
||||
@ -14,6 +12,7 @@ import {
|
||||
type UsernameProviderData,
|
||||
type OAuthProviderData,
|
||||
} from 'wasp/auth/utils'
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
{=/ isAuthEnabled =}
|
||||
import { type _Entity } from "./taggedEntities"
|
||||
import { type Payload } from "./serialization";
|
||||
@ -88,20 +87,5 @@ type Context<Entities extends _Entity[]> = Expand<{
|
||||
{=# isAuthEnabled =}
|
||||
type ContextWithUser<Entities extends _Entity[]> = Expand<Context<Entities> & { user?: AuthUser }>
|
||||
|
||||
// TODO: This type must match the logic in auth/session.js (if we remove the
|
||||
// password field from the object there, we must do the same here). Ideally,
|
||||
// these two things would live in the same place:
|
||||
// https://github.com/wasp-lang/wasp/issues/965
|
||||
|
||||
export type DeserializedAuthIdentity = Expand<Omit<{= authIdentityEntityName =}, 'providerData'> & {
|
||||
providerData: Omit<EmailProviderData, 'password'> | Omit<UsernameProviderData, 'password'> | OAuthProviderData
|
||||
}>
|
||||
|
||||
export type AuthUser = {= userEntityName =} & {
|
||||
{= authFieldOnUserEntityName =}: {= authEntityName =} & {
|
||||
{= identitiesFieldOnAuthEntityName =}: DeserializedAuthIdentity[]
|
||||
} | null
|
||||
}
|
||||
|
||||
export type { ProviderName } from 'wasp/auth/utils'
|
||||
{=/ isAuthEnabled =}
|
||||
|
117
waspc/data/Generator/templates/sdk/wasp/server/auth/user.ts
Normal file
117
waspc/data/Generator/templates/sdk/wasp/server/auth/user.ts
Normal file
@ -0,0 +1,117 @@
|
||||
{{={= =}=}}
|
||||
import {
|
||||
type {= userEntityName =},
|
||||
type {= authEntityName =},
|
||||
type {= authIdentityEntityName =},
|
||||
} from '../../entities/index.js'
|
||||
import {
|
||||
type PossibleProviderData,
|
||||
deserializeAndSanitizeProviderData
|
||||
} from '../../auth/utils.js'
|
||||
import { type ProviderName } from '../_types/index.js'
|
||||
import { getFirstProviderUserId } from '../../auth/user.js'
|
||||
import { Expand } from '../../universal/types.js'
|
||||
|
||||
// PUBLIC API
|
||||
/**
|
||||
* Ideally, we'd do something like this:
|
||||
* ```
|
||||
* export type AuthUser = ReturnType<typeof createAuthUser>
|
||||
* ```
|
||||
* to get the benefits of the createAuthUser and the AuthUser type being in sync.
|
||||
*
|
||||
* But since we are not using strict mode, the inferred return type of createAuthUser
|
||||
* is not correct. So we have to define the AuthUser type manually.
|
||||
*
|
||||
* TODO: Change this once/if we switch to strict mode. https://github.com/wasp-lang/wasp/issues/1938
|
||||
*/
|
||||
export type AuthUser = Omit<UserEntityWithAuth, '{= authFieldOnUserEntityName =}'> & {
|
||||
identities: {
|
||||
{=# enabledProviders.isEmailAuthEnabled =}
|
||||
email: Expand<UserFacingProviderData<'email'>> | null
|
||||
{=/ enabledProviders.isEmailAuthEnabled =}
|
||||
{=# enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
username: Expand<UserFacingProviderData<'username'>> | null
|
||||
{=/ enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
{=# enabledProviders.isGoogleAuthEnabled =}
|
||||
google: Expand<UserFacingProviderData<'google'>> | null
|
||||
{=/ enabledProviders.isGoogleAuthEnabled =}
|
||||
{=# enabledProviders.isKeycloakAuthEnabled =}
|
||||
keycloak: Expand<UserFacingProviderData<'keycloak'>> | null
|
||||
{=/ enabledProviders.isKeycloakAuthEnabled =}
|
||||
{=# enabledProviders.isGitHubAuthEnabled =}
|
||||
github: Expand<UserFacingProviderData<'github'>> | null
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
},
|
||||
getFirstProviderUserId: () => string | null,
|
||||
}
|
||||
|
||||
type UserFacingProviderData<PN extends ProviderName> = {
|
||||
id: string
|
||||
} & Omit<PossibleProviderData[PN], 'hashedPassword'>
|
||||
|
||||
// PRIVATE API
|
||||
export type UserEntityWithAuth = {= userEntityName =} & {
|
||||
{= authFieldOnUserEntityName =}: AuthEntityWithIdentities | null
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export type AuthEntityWithIdentities = {= authEntityName =} & {
|
||||
{= identitiesFieldOnAuthEntityName =}: {= authIdentityEntityName =}[]
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export function createAuthUser(user: UserEntityWithAuth): AuthUser {
|
||||
const { {= authFieldOnUserEntityName =}, ...rest } = user
|
||||
if (!{= authFieldOnUserEntityName =}) {
|
||||
throw new Error(`🐝 Error: trying to create a user without auth data.
|
||||
This should never happen, but it did which means there is a bug in the code.`)
|
||||
}
|
||||
const identities = {
|
||||
{=# enabledProviders.isEmailAuthEnabled =}
|
||||
email: getProviderInfo<'email'>({= authFieldOnUserEntityName =}, 'email'),
|
||||
{=/ enabledProviders.isEmailAuthEnabled =}
|
||||
{=# enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
username: getProviderInfo<'username'>({= authFieldOnUserEntityName =}, 'username'),
|
||||
{=/ enabledProviders.isUsernameAndPasswordAuthEnabled =}
|
||||
{=# enabledProviders.isGoogleAuthEnabled =}
|
||||
google: getProviderInfo<'google'>({= authFieldOnUserEntityName =}, 'google'),
|
||||
{=/ enabledProviders.isGoogleAuthEnabled =}
|
||||
{=# enabledProviders.isKeycloakAuthEnabled =}
|
||||
keycloak: getProviderInfo<'keycloak'>({= authFieldOnUserEntityName =}, 'keycloak'),
|
||||
{=/ enabledProviders.isKeycloakAuthEnabled =}
|
||||
{=# enabledProviders.isGitHubAuthEnabled =}
|
||||
github: getProviderInfo<'github'>({= authFieldOnUserEntityName =}, 'github'),
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
}
|
||||
return {
|
||||
...rest,
|
||||
identities,
|
||||
getFirstProviderUserId: () => getFirstProviderUserId(user),
|
||||
}
|
||||
}
|
||||
|
||||
function getProviderInfo<PN extends ProviderName>(
|
||||
auth: AuthEntityWithIdentities,
|
||||
providerName: PN
|
||||
):
|
||||
| UserFacingProviderData<PN>
|
||||
| null {
|
||||
const identity = getIdentity(auth, providerName)
|
||||
if (!identity) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
...deserializeAndSanitizeProviderData<PN>(identity.providerData, {
|
||||
shouldRemovePasswordField: true,
|
||||
}),
|
||||
id: identity.providerUserId,
|
||||
}
|
||||
}
|
||||
|
||||
function getIdentity(
|
||||
auth: AuthEntityWithIdentities,
|
||||
providerName: ProviderName
|
||||
): {= authIdentityEntityName =} | null {
|
||||
return auth.{= identitiesFieldOnAuthEntityName =}.find((i) => i.providerName === providerName) ?? null
|
||||
}
|
@ -264,6 +264,9 @@ waspComplexTest/.wasp/out/sdk/wasp/dist/server/api/index.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/user.d.ts
|
||||
waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.js
|
||||
waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.js.map
|
||||
waspComplexTest/.wasp/out/sdk/wasp/dist/server/config.d.ts
|
||||
waspComplexTest/.wasp/out/sdk/wasp/dist/server/config.js
|
||||
waspComplexTest/.wasp/out/sdk/wasp/dist/server/config.js.map
|
||||
@ -375,6 +378,7 @@ waspComplexTest/.wasp/out/sdk/wasp/server/_types/serialization.ts
|
||||
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/index.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
|
||||
waspComplexTest/.wasp/out/sdk/wasp/server/crud/tasks.ts
|
||||
|
@ -95,7 +95,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/index.ts"
|
||||
],
|
||||
"f79d9aa0c8c5c0365c30c4db4e9c35946e1650266eeea1cf06fcce191211c911"
|
||||
"a846aace0af3913411b6c77039cb04d37311d40e5e1f978fd960b38409ec9b66"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -137,28 +137,28 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/session.ts"
|
||||
],
|
||||
"b682047557c966d8f45e4ec1e27a0b4773cd5765af7864990edac6c0e3997db1"
|
||||
"03f78291fb369497a07de2f5c2181857e80f158f5eb590b6c095e98c95ff9d14"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/types.ts"
|
||||
],
|
||||
"81afde3e152e92d6888f5cf67ab60711ab9cd156e09408d2a618dd02a71371fe"
|
||||
"642e51e5d44dcc8a2730af4da67bb8012b39c4df39a724e7ee8540d138f610be"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/useAuth.ts"
|
||||
],
|
||||
"48a0e49de404ae16e84eeb3194295c1114c30c105ca227d0a4c428faf2b3a699"
|
||||
"2fc317b5939816da10ae7709768c4dae52f541aa274620c1896c9d423b0c3122"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/user.ts"
|
||||
],
|
||||
"07164ea63b1aa89fa6279994f42f19b6f3552f897e9d9214c8799cadd9089be3"
|
||||
"2b1911715a41f242cf3f4b8e3954db7146736300fa6888ec8090204eef6b0b6e"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -480,7 +480,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/server/_types/index.ts"
|
||||
],
|
||||
"6993d64660f25759a15ccde4cd1f3a18e651044d362a348912203c0ba2284588"
|
||||
"7a9bd946ac8b55a110a6d1ed93040c1ec4def2104b0de58961b56c678d31ab91"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -510,6 +510,13 @@
|
||||
],
|
||||
"61745079707aa8a4134e140b5cf33d5c9977d997da924de606cf606871438455"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"../out/sdk/wasp/server/auth/user.ts"
|
||||
],
|
||||
"76a7add6b2c64e66b583f69320e5cd8ee2595cef709b86d9264cb8d63f7b8adf"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
|
@ -1,4 +1,11 @@
|
||||
// PUBLIC
|
||||
export type { AuthUser } from '../server/_types'
|
||||
// PUBLIC API
|
||||
export {
|
||||
getEmail,
|
||||
getUsername,
|
||||
getFirstProviderUserId,
|
||||
} from './user.js'
|
||||
|
||||
export { getEmail, getUsername, getFirstProviderUserId, findUserIdentity } from './user.js'
|
||||
// PUBLIC API
|
||||
export {
|
||||
type AuthUser,
|
||||
} from '../server/auth/user.js';
|
||||
|
@ -5,12 +5,10 @@ import { type AuthUser } from 'wasp/auth'
|
||||
|
||||
import { auth } from "./lucia.js";
|
||||
import type { Session } from "lucia";
|
||||
import {
|
||||
throwInvalidCredentialsError,
|
||||
deserializeAndSanitizeProviderData,
|
||||
} from "./utils.js";
|
||||
import { throwInvalidCredentialsError } from "./utils.js";
|
||||
|
||||
import { prisma } from 'wasp/server';
|
||||
import { createAuthUser } from "../server/auth/user.js";
|
||||
|
||||
// PRIVATE API
|
||||
// Creates a new session for the `authId` in the database
|
||||
@ -80,29 +78,7 @@ async function getUser(userId: User['id']): Promise<AuthUser> {
|
||||
throwInvalidCredentialsError()
|
||||
}
|
||||
|
||||
// TODO: This logic must match the type in _types/index.ts (if we remove the
|
||||
// password field from the object here, we must to do the same there).
|
||||
// Ideally, these two things would live in the same place:
|
||||
// https://github.com/wasp-lang/wasp/issues/965
|
||||
const deserializedIdentities = user.auth.identities.map((identity) => {
|
||||
const deserializedProviderData = deserializeAndSanitizeProviderData(
|
||||
identity.providerData,
|
||||
{
|
||||
shouldRemovePasswordField: true,
|
||||
}
|
||||
)
|
||||
return {
|
||||
...identity,
|
||||
providerData: deserializedProviderData,
|
||||
}
|
||||
})
|
||||
return {
|
||||
...user,
|
||||
auth: {
|
||||
...user.auth,
|
||||
identities: deserializedIdentities,
|
||||
},
|
||||
}
|
||||
return createAuthUser(user);
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
|
@ -1,2 +1,2 @@
|
||||
// todo(filip): turn into a proper import/path
|
||||
export type { AuthUser, ProviderName, DeserializedAuthIdentity } from 'wasp/server/_types'
|
||||
export type { ProviderName } from 'wasp/server/_types'
|
||||
|
@ -2,7 +2,7 @@ import { deserialize as superjsonDeserialize } from 'superjson'
|
||||
import { useQuery, addMetadataToQuery } from 'wasp/client/operations'
|
||||
import { api, handleApiError } from 'wasp/client/api'
|
||||
import { HttpMethod } from 'wasp/client'
|
||||
import type { AuthUser } from './types'
|
||||
import type { AuthUser } from '../server/auth/user.js'
|
||||
import { UseQueryResult } from '@tanstack/react-query'
|
||||
|
||||
// PUBLIC API
|
||||
|
@ -1,17 +1,23 @@
|
||||
import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types'
|
||||
import { type AuthIdentity } from '../entities/index.js'
|
||||
import { type ProviderName } from '../server/_types/index.js'
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
*/
|
||||
import { type UserEntityWithAuth } from '../server/auth/user.js'
|
||||
|
||||
// PUBLIC API
|
||||
export function getEmail(user: AuthUser): string | null {
|
||||
export function getEmail(user: UserEntityWithAuth): string | null {
|
||||
return findUserIdentity(user, "email")?.providerUserId ?? null;
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function getUsername(user: AuthUser): string | null {
|
||||
export function getUsername(user: UserEntityWithAuth): string | null {
|
||||
return findUserIdentity(user, "username")?.providerUserId ?? null;
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function getFirstProviderUserId(user?: AuthUser): string | null {
|
||||
export function getFirstProviderUserId(user?: UserEntityWithAuth): string | null {
|
||||
if (!user || !user.auth || !user.auth.identities || user.auth.identities.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@ -19,9 +25,11 @@ export function getFirstProviderUserId(user?: AuthUser): string | null {
|
||||
return user.auth.identities[0].providerUserId ?? null;
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
export function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined {
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): AuthIdentity | null {
|
||||
if (!user.auth) {
|
||||
return null;
|
||||
}
|
||||
return user.auth.identities.find(
|
||||
(identity) => identity.providerName === providerName
|
||||
);
|
||||
) ?? null;
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export type { AuthUser } from '../server/_types';
|
||||
export { getEmail, getUsername, getFirstProviderUserId, findUserIdentity } from './user.js';
|
||||
export { getEmail, getUsername, getFirstProviderUserId, } from './user.js';
|
||||
export { type AuthUser, } from '../server/auth/user.js';
|
||||
|
@ -1,2 +1,3 @@
|
||||
export { getEmail, getUsername, getFirstProviderUserId, findUserIdentity } from './user.js';
|
||||
// PUBLIC API
|
||||
export { getEmail, getUsername, getFirstProviderUserId, } from './user.js';
|
||||
//# sourceMappingURL=index.js.map
|
@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../auth/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../auth/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EACL,QAAQ,EACR,WAAW,EACX,sBAAsB,GACvB,MAAM,WAAW,CAAA"}
|
@ -1,6 +1,7 @@
|
||||
import { auth } from "./lucia.js";
|
||||
import { throwInvalidCredentialsError, deserializeAndSanitizeProviderData, } from "./utils.js";
|
||||
import { throwInvalidCredentialsError } from "./utils.js";
|
||||
import { prisma } from 'wasp/server';
|
||||
import { createAuthUser } from "../server/auth/user.js";
|
||||
// PRIVATE API
|
||||
// Creates a new session for the `authId` in the database
|
||||
export async function createSession(authId) {
|
||||
@ -53,17 +54,7 @@ async function getUser(userId) {
|
||||
if (!user) {
|
||||
throwInvalidCredentialsError();
|
||||
}
|
||||
// TODO: This logic must match the type in _types/index.ts (if we remove the
|
||||
// password field from the object here, we must to do the same there).
|
||||
// Ideally, these two things would live in the same place:
|
||||
// https://github.com/wasp-lang/wasp/issues/965
|
||||
const deserializedIdentities = user.auth.identities.map((identity) => {
|
||||
const deserializedProviderData = deserializeAndSanitizeProviderData(identity.providerData, {
|
||||
shouldRemovePasswordField: true,
|
||||
});
|
||||
return Object.assign(Object.assign({}, identity), { providerData: deserializedProviderData });
|
||||
});
|
||||
return Object.assign(Object.assign({}, user), { auth: Object.assign(Object.assign({}, user.auth), { identities: deserializedIdentities }) });
|
||||
return createAuthUser(user);
|
||||
}
|
||||
// PRIVATE API
|
||||
export function invalidateSession(sessionId) {
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../auth/session.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,OAAO,EACL,4BAA4B,EAC5B,kCAAkC,GACnC,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,cAAc;AACd,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc;IAChD,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,gCAAgC,CAAC,GAAmB;IAIxE,MAAM,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAEzD,IAAI,OAAO,mBAAmB,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,mBAAmB,CAAC,CAAC;IAC5D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,OAAO,8BAA8B,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,SAAiB;IAIpE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAE5E,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;QAC5B,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO;QACP,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC;KACvC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,MAAkB;IACvC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI;SAC3B,UAAU,CAAC;QACV,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QACrB,OAAO,EAAE;YACP,IAAI,EAAE;gBACJ,OAAO,EAAE;oBACP,UAAU,EAAE,IAAI;iBACjB;aACF;SACF;KACF,CAAC,CAAA;IAEJ,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,4BAA4B,EAAE,CAAA;IAChC,CAAC;IAED,4EAA4E;IAC5E,sEAAsE;IACtE,0DAA0D;IAC1D,+CAA+C;IAC/C,MAAM,sBAAsB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QACnE,MAAM,wBAAwB,GAAG,kCAAkC,CACjE,QAAQ,CAAC,YAAY,EACrB;YACE,yBAAyB,EAAE,IAAI;SAChC,CACF,CAAA;QACD,uCACK,QAAQ,KACX,YAAY,EAAE,wBAAwB,IACvC;IACH,CAAC,CAAC,CAAA;IACF,uCACK,IAAI,KACP,IAAI,kCACC,IAAI,CAAC,IAAI,KACZ,UAAU,EAAE,sBAAsB,OAErC;AACH,CAAC;AAED,cAAc;AACd,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC3C,CAAC"}
|
||||
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../auth/session.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAE1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,cAAc;AACd,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc;IAChD,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,gCAAgC,CAAC,GAAmB;IAIxE,MAAM,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAEzD,IAAI,OAAO,mBAAmB,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,mBAAmB,CAAC,CAAC;IAC5D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,OAAO,8BAA8B,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,SAAiB;IAIpE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAE5E,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;QAC5B,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO;QACP,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC;KACvC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,MAAkB;IACvC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI;SAC3B,UAAU,CAAC;QACV,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QACrB,OAAO,EAAE;YACP,IAAI,EAAE;gBACJ,OAAO,EAAE;oBACP,UAAU,EAAE,IAAI;iBACjB;aACF;SACF;KACF,CAAC,CAAA;IAEJ,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,4BAA4B,EAAE,CAAA;IAChC,CAAC;IAED,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,cAAc;AACd,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC3C,CAAC"}
|
@ -1 +1 @@
|
||||
export type { AuthUser, ProviderName, DeserializedAuthIdentity } from 'wasp/server/_types';
|
||||
export type { ProviderName } from 'wasp/server/_types';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { AuthUser } from './types';
|
||||
import type { AuthUser } from '../server/auth/user.js';
|
||||
import { UseQueryResult } from '@tanstack/react-query';
|
||||
export declare const getMe: () => Promise<AuthUser | null>;
|
||||
export default function useAuth(queryFnArgs?: unknown, config?: any): UseQueryResult<AuthUser>;
|
||||
|
@ -1,5 +1,8 @@
|
||||
import type { AuthUser, ProviderName, DeserializedAuthIdentity } from './types';
|
||||
export declare function getEmail(user: AuthUser): string | null;
|
||||
export declare function getUsername(user: AuthUser): string | null;
|
||||
export declare function getFirstProviderUserId(user?: AuthUser): string | null;
|
||||
export declare function findUserIdentity(user: AuthUser, providerName: ProviderName): DeserializedAuthIdentity | undefined;
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
*/
|
||||
import { type UserEntityWithAuth } from '../server/auth/user.js';
|
||||
export declare function getEmail(user: UserEntityWithAuth): string | null;
|
||||
export declare function getUsername(user: UserEntityWithAuth): string | null;
|
||||
export declare function getFirstProviderUserId(user?: UserEntityWithAuth): string | null;
|
||||
|
@ -16,8 +16,11 @@ export function getFirstProviderUserId(user) {
|
||||
}
|
||||
return (_a = user.auth.identities[0].providerUserId) !== null && _a !== void 0 ? _a : null;
|
||||
}
|
||||
// PUBLIC API
|
||||
export function findUserIdentity(user, providerName) {
|
||||
return user.auth.identities.find((identity) => identity.providerName === providerName);
|
||||
function findUserIdentity(user, providerName) {
|
||||
var _a;
|
||||
if (!user.auth) {
|
||||
return null;
|
||||
}
|
||||
return (_a = user.auth.identities.find((identity) => identity.providerName === providerName)) !== null && _a !== void 0 ? _a : null;
|
||||
}
|
||||
//# sourceMappingURL=user.js.map
|
@ -1 +1 @@
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../auth/user.ts"],"names":[],"mappings":"AAEA,aAAa;AACb,MAAM,UAAU,QAAQ,CAAC,IAAc;;IACrC,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACjE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,WAAW,CAAC,IAAc;;IACxC,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACpE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,sBAAsB,CAAC,IAAe;;IACpD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,mCAAI,IAAI,CAAC;AACxD,CAAC;AAED,aAAa;AACb,MAAM,UAAU,gBAAgB,CAAC,IAAc,EAAE,YAA0B;IACzE,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAC9B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,KAAK,YAAY,CACrD,CAAC;AACJ,CAAC"}
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../auth/user.ts"],"names":[],"mappings":"AAQA,aAAa;AACb,MAAM,UAAU,QAAQ,CAAC,IAAwB;;IAC/C,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACjE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,WAAW,CAAC,IAAwB;;IAClD,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACpE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,sBAAsB,CAAC,IAAyB;;IAC9D,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,mCAAI,IAAI,CAAC;AACxD,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAwB,EAAE,YAA0B;;IAC5E,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAC9B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,KAAK,YAAY,CACrD,mCAAI,IAAI,CAAC;AACZ,CAAC"}
|
@ -2,8 +2,7 @@ import { type Expand } from 'wasp/universal/types';
|
||||
import { type Request, type Response } from 'express';
|
||||
import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core';
|
||||
import { prisma } from 'wasp/server';
|
||||
import { type User, type Auth, type AuthIdentity } from "wasp/entities";
|
||||
import { type EmailProviderData, type UsernameProviderData, type OAuthProviderData } from 'wasp/auth/utils';
|
||||
import { type AuthUser } from 'wasp/auth';
|
||||
import { type _Entity } from "./taggedEntities";
|
||||
import { type Payload } from "./serialization";
|
||||
export * from "./taggedEntities";
|
||||
@ -30,12 +29,4 @@ type Context<Entities extends _Entity[]> = Expand<{
|
||||
type ContextWithUser<Entities extends _Entity[]> = Expand<Context<Entities> & {
|
||||
user?: AuthUser;
|
||||
}>;
|
||||
export type DeserializedAuthIdentity = Expand<Omit<AuthIdentity, 'providerData'> & {
|
||||
providerData: Omit<EmailProviderData, 'password'> | Omit<UsernameProviderData, 'password'> | OAuthProviderData;
|
||||
}>;
|
||||
export type AuthUser = User & {
|
||||
auth: Auth & {
|
||||
identities: DeserializedAuthIdentity[];
|
||||
} | null;
|
||||
};
|
||||
export type { ProviderName } from 'wasp/auth/utils';
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/_types/index.ts"],"names":[],"mappings":"AAiBA,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/_types/index.ts"],"names":[],"mappings":"AAgBA,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA"}
|
33
waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.d.ts
generated
vendored
Normal file
33
waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.d.ts
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
import { type User, type Auth, type AuthIdentity } from '../../entities/index.js';
|
||||
import { type PossibleProviderData } from '../../auth/utils.js';
|
||||
import { type ProviderName } from '../_types/index.js';
|
||||
import { Expand } from '../../universal/types.js';
|
||||
/**
|
||||
* Ideally, we'd do something like this:
|
||||
* ```
|
||||
* export type AuthUser = ReturnType<typeof createAuthUser>
|
||||
* ```
|
||||
* to get the benefits of the createAuthUser and the AuthUser type being in sync.
|
||||
*
|
||||
* But since we are not using strict mode, the inferred return type of createAuthUser
|
||||
* is not correct. So we have to define the AuthUser type manually.
|
||||
*
|
||||
* TODO: Change this once/if we switch to strict mode. https://github.com/wasp-lang/wasp/issues/1938
|
||||
*/
|
||||
export type AuthUser = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
identities: {
|
||||
google: Expand<UserFacingProviderData<'google'>> | null;
|
||||
};
|
||||
getFirstProviderUserId: () => string | null;
|
||||
};
|
||||
type UserFacingProviderData<PN extends ProviderName> = {
|
||||
id: string;
|
||||
} & Omit<PossibleProviderData[PN], 'hashedPassword'>;
|
||||
export type UserEntityWithAuth = User & {
|
||||
auth: AuthEntityWithIdentities | null;
|
||||
};
|
||||
export type AuthEntityWithIdentities = Auth & {
|
||||
identities: AuthIdentity[];
|
||||
};
|
||||
export declare function createAuthUser(user: UserEntityWithAuth): AuthUser;
|
||||
export {};
|
39
waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.js
generated
vendored
Normal file
39
waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.js
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
import { deserializeAndSanitizeProviderData } from '../../auth/utils.js';
|
||||
import { getFirstProviderUserId } from '../../auth/user.js';
|
||||
// PRIVATE API
|
||||
export function createAuthUser(user) {
|
||||
const { auth } = user, rest = __rest(user, ["auth"]);
|
||||
if (!auth) {
|
||||
throw new Error(`🐝 Error: trying to create a user without auth data.
|
||||
This should never happen, but it did which means there is a bug in the code.`);
|
||||
}
|
||||
const identities = {
|
||||
google: getProviderInfo(auth, 'google'),
|
||||
};
|
||||
return Object.assign(Object.assign({}, rest), { identities, getFirstProviderUserId: () => getFirstProviderUserId(user) });
|
||||
}
|
||||
function getProviderInfo(auth, providerName) {
|
||||
const identity = getIdentity(auth, providerName);
|
||||
if (!identity) {
|
||||
return null;
|
||||
}
|
||||
return Object.assign(Object.assign({}, deserializeAndSanitizeProviderData(identity.providerData, {
|
||||
shouldRemovePasswordField: true,
|
||||
})), { id: identity.providerUserId });
|
||||
}
|
||||
function getIdentity(auth, providerName) {
|
||||
var _a;
|
||||
return (_a = auth.identities.find((i) => i.providerName === providerName)) !== null && _a !== void 0 ? _a : null;
|
||||
}
|
||||
//# sourceMappingURL=user.js.map
|
1
waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.js.map
generated
vendored
Normal file
1
waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/.wasp/out/sdk/wasp/dist/server/auth/user.js.map
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../../server/auth/user.ts"],"names":[],"mappings":";;;;;;;;;;;AAKA,OAAO,EAEL,kCAAkC,EACnC,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAqC3D,cAAc;AACd,MAAM,UAAU,cAAc,CAAC,IAAwB;IACrD,MAAM,EAAE,IAAI,KAAc,IAAI,EAAb,IAAI,UAAK,IAAI,EAAxB,QAAiB,CAAO,CAAA;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC;6EACyD,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,UAAU,GAAG;QACjB,MAAM,EAAE,eAAe,CAAW,IAAI,EAAE,QAAQ,CAAC;KAClD,CAAA;IACD,uCACK,IAAI,KACP,UAAU,EACV,sBAAsB,EAAE,GAAG,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAC3D;AACH,CAAC;AAED,SAAS,eAAe,CACtB,IAA8B,EAC9B,YAAgB;IAIhB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAA;IACb,CAAC;IACD,uCACK,kCAAkC,CAAK,QAAQ,CAAC,YAAY,EAAE;QAC/D,yBAAyB,EAAE,IAAI;KAChC,CAAC,KACF,EAAE,EAAE,QAAQ,CAAC,cAAc,IAC5B;AACH,CAAC;AAED,SAAS,WAAW,CAClB,IAA8B,EAC9B,YAA0B;;IAE1B,OAAO,MAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,mCAAI,IAAI,CAAA;AAC7E,CAAC"}
|
@ -3,8 +3,6 @@ import { type Request, type Response } from 'express'
|
||||
import { type ParamsDictionary as ExpressParams, type Query as ExpressQuery } from 'express-serve-static-core'
|
||||
import { prisma } from 'wasp/server'
|
||||
import {
|
||||
type User,
|
||||
type Auth,
|
||||
type AuthIdentity,
|
||||
} from "wasp/entities"
|
||||
import {
|
||||
@ -12,6 +10,7 @@ import {
|
||||
type UsernameProviderData,
|
||||
type OAuthProviderData,
|
||||
} from 'wasp/auth/utils'
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import { type _Entity } from "./taggedEntities"
|
||||
import { type Payload } from "./serialization";
|
||||
|
||||
@ -82,19 +81,4 @@ type Context<Entities extends _Entity[]> = Expand<{
|
||||
|
||||
type ContextWithUser<Entities extends _Entity[]> = Expand<Context<Entities> & { user?: AuthUser }>
|
||||
|
||||
// TODO: This type must match the logic in auth/session.js (if we remove the
|
||||
// password field from the object there, we must do the same here). Ideally,
|
||||
// these two things would live in the same place:
|
||||
// https://github.com/wasp-lang/wasp/issues/965
|
||||
|
||||
export type DeserializedAuthIdentity = Expand<Omit<AuthIdentity, 'providerData'> & {
|
||||
providerData: Omit<EmailProviderData, 'password'> | Omit<UsernameProviderData, 'password'> | OAuthProviderData
|
||||
}>
|
||||
|
||||
export type AuthUser = User & {
|
||||
auth: Auth & {
|
||||
identities: DeserializedAuthIdentity[]
|
||||
} | null
|
||||
}
|
||||
|
||||
export type { ProviderName } from 'wasp/auth/utils'
|
||||
|
@ -0,0 +1,88 @@
|
||||
import {
|
||||
type User,
|
||||
type Auth,
|
||||
type AuthIdentity,
|
||||
} from '../../entities/index.js'
|
||||
import {
|
||||
type PossibleProviderData,
|
||||
deserializeAndSanitizeProviderData
|
||||
} from '../../auth/utils.js'
|
||||
import { type ProviderName } from '../_types/index.js'
|
||||
import { getFirstProviderUserId } from '../../auth/user.js'
|
||||
import { Expand } from '../../universal/types.js'
|
||||
|
||||
// PUBLIC API
|
||||
/**
|
||||
* Ideally, we'd do something like this:
|
||||
* ```
|
||||
* export type AuthUser = ReturnType<typeof createAuthUser>
|
||||
* ```
|
||||
* to get the benefits of the createAuthUser and the AuthUser type being in sync.
|
||||
*
|
||||
* But since we are not using strict mode, the inferred return type of createAuthUser
|
||||
* is not correct. So we have to define the AuthUser type manually.
|
||||
*
|
||||
* TODO: Change this once/if we switch to strict mode. https://github.com/wasp-lang/wasp/issues/1938
|
||||
*/
|
||||
export type AuthUser = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
identities: {
|
||||
google: Expand<UserFacingProviderData<'google'>> | null
|
||||
},
|
||||
getFirstProviderUserId: () => string | null,
|
||||
}
|
||||
|
||||
type UserFacingProviderData<PN extends ProviderName> = {
|
||||
id: string
|
||||
} & Omit<PossibleProviderData[PN], 'hashedPassword'>
|
||||
|
||||
// PRIVATE API
|
||||
export type UserEntityWithAuth = User & {
|
||||
auth: AuthEntityWithIdentities | null
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export type AuthEntityWithIdentities = Auth & {
|
||||
identities: AuthIdentity[]
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export function createAuthUser(user: UserEntityWithAuth): AuthUser {
|
||||
const { auth, ...rest } = user
|
||||
if (!auth) {
|
||||
throw new Error(`🐝 Error: trying to create a user without auth data.
|
||||
This should never happen, but it did which means there is a bug in the code.`)
|
||||
}
|
||||
const identities = {
|
||||
google: getProviderInfo<'google'>(auth, 'google'),
|
||||
}
|
||||
return {
|
||||
...rest,
|
||||
identities,
|
||||
getFirstProviderUserId: () => getFirstProviderUserId(user),
|
||||
}
|
||||
}
|
||||
|
||||
function getProviderInfo<PN extends ProviderName>(
|
||||
auth: AuthEntityWithIdentities,
|
||||
providerName: PN
|
||||
):
|
||||
| UserFacingProviderData<PN>
|
||||
| null {
|
||||
const identity = getIdentity(auth, providerName)
|
||||
if (!identity) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
...deserializeAndSanitizeProviderData<PN>(identity.providerData, {
|
||||
shouldRemovePasswordField: true,
|
||||
}),
|
||||
id: identity.providerUserId,
|
||||
}
|
||||
}
|
||||
|
||||
function getIdentity(
|
||||
auth: AuthEntityWithIdentities,
|
||||
providerName: ProviderName
|
||||
): AuthIdentity | null {
|
||||
return auth.identities.find((i) => i.providerName === providerName) ?? null
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { Link, routes } from "wasp/client/router";
|
||||
import { logout } from "wasp/client/auth";
|
||||
import { getUsername } from "wasp/auth";
|
||||
import { Link, routes } from 'wasp/client/router'
|
||||
import { logout } from 'wasp/client/auth'
|
||||
import { getUsername } from 'wasp/auth'
|
||||
|
||||
import './Main.css'
|
||||
|
||||
|
@ -27,7 +27,12 @@ export const getAllTasks = (async (args, context) => {
|
||||
include: {
|
||||
auth: {
|
||||
include: {
|
||||
identities: true,
|
||||
identities: {
|
||||
select: {
|
||||
providerName: true,
|
||||
providerUserId: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -22,7 +22,7 @@ app todoApp {
|
||||
configFn: import { config } from "@src/auth/github.js",
|
||||
userSignupFields: import { userSignupFields } from "@src/auth/github.js"
|
||||
},
|
||||
keycloak: {},
|
||||
// keycloak: {},
|
||||
email: {
|
||||
userSignupFields: import { userSignupFields } from "@src/auth/email.js",
|
||||
fromField: {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { type AuthUser as User } from 'wasp/auth'
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import { mockServer, renderInContext } from 'wasp/client/test'
|
||||
import { getTasks, getDate } from 'wasp/client/operations'
|
||||
import { test, expect } from 'vitest'
|
||||
@ -36,21 +36,12 @@ test('handles mock data', async () => {
|
||||
})
|
||||
|
||||
const mockUser = {
|
||||
id: 12,
|
||||
auth: {
|
||||
id: '123',
|
||||
userId: 12,
|
||||
identities: [
|
||||
{
|
||||
authId: '123',
|
||||
providerName: 'email',
|
||||
providerUserId: 'elon@tesla.com',
|
||||
providerData: '',
|
||||
},
|
||||
],
|
||||
identities: {
|
||||
email: {
|
||||
id: 'elon@tesla.com',
|
||||
},
|
||||
},
|
||||
address: null,
|
||||
} satisfies User
|
||||
} as AuthUser
|
||||
|
||||
test('handles multiple mock data sources', async () => {
|
||||
mockQuery(getMe, mockUser)
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { getFirstProviderUserId } from "wasp/auth";
|
||||
import { type MiddlewareConfigFn } from "wasp/server";
|
||||
import { type BarBaz, type FooBar, type WebhookCallback } from "wasp/server/api";
|
||||
import { type MiddlewareConfigFn } from 'wasp/server'
|
||||
import { type BarBaz, type FooBar, type WebhookCallback } from 'wasp/server/api'
|
||||
import express from 'express'
|
||||
|
||||
export const fooBar: FooBar = (_req, res, context) => {
|
||||
const username = getFirstProviderUserId(context?.user) ?? 'Anonymous'
|
||||
const username = context.user?.getFirstProviderUserId()
|
||||
|
||||
res.json({ msg: `Hello, ${username}!` })
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { PrismaClient } from '@prisma/client/index.js'
|
||||
import { type DbSeedFn } from "wasp/server";
|
||||
import { type DbSeedFn } from 'wasp/server'
|
||||
import { sanitizeAndSerializeProviderData } from 'wasp/server/auth'
|
||||
import { createTask } from './actions.js'
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
|
||||
async function createUser(prismaClient: PrismaClient, data: any) {
|
||||
const newUser = await prismaClient.user.create({
|
||||
@ -34,14 +35,9 @@ async function createUser(prismaClient: PrismaClient, data: any) {
|
||||
},
|
||||
})
|
||||
|
||||
if (newUser.auth?.identities) {
|
||||
newUser.auth.identities = newUser.auth.identities.map((identity) => {
|
||||
identity.providerData = JSON.parse(identity.providerData)
|
||||
return identity
|
||||
})
|
||||
}
|
||||
|
||||
return newUser
|
||||
return {
|
||||
id: newUser.id,
|
||||
} as AuthUser
|
||||
}
|
||||
|
||||
export const devSeedSimple: DbSeedFn = async (prismaClient) => {
|
||||
|
@ -1,16 +1,20 @@
|
||||
import { type AuthUser as User } from "wasp/auth";
|
||||
import { type ServerToClientPayload, useSocket, useSocketListener } from "wasp/client/webSocket";
|
||||
import { Link, routes } from "wasp/client/router";
|
||||
import { api } from "wasp/client/api";
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import {
|
||||
type ServerToClientPayload,
|
||||
useSocket,
|
||||
useSocketListener,
|
||||
} from 'wasp/client/webSocket'
|
||||
import { Link, routes } from 'wasp/client/router'
|
||||
import { api } from 'wasp/client/api'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { getName, getProviderData } from '../user'
|
||||
import { getName } from '../user'
|
||||
|
||||
async function fetchCustomRoute() {
|
||||
const res = await api.get('/foo/bar')
|
||||
console.log(res.data)
|
||||
}
|
||||
|
||||
export const ProfilePage = ({ user }: { user: User }) => {
|
||||
export const ProfilePage = ({ user }: { user: AuthUser }) => {
|
||||
const [messages, setMessages] = useState<
|
||||
ServerToClientPayload<'chatMessage'>[]
|
||||
>([])
|
||||
@ -41,15 +45,13 @@ export const ProfilePage = ({ user }: { user: User }) => {
|
||||
))
|
||||
const connectionIcon = isConnected ? '🟢' : '🔴'
|
||||
|
||||
const providerData = getProviderData(user)
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Profile page</h2>
|
||||
<div>
|
||||
Hello <strong>{getName(user)}</strong>! Your status is{' '}
|
||||
<strong>
|
||||
{providerData && providerData.isEmailVerified
|
||||
{user.identities.email && user.identities.email.isEmailVerified
|
||||
? 'verfied'
|
||||
: 'unverified'}
|
||||
</strong>
|
||||
|
@ -1,42 +1,27 @@
|
||||
import { getEmail, findUserIdentity, type AuthUser as User } from 'wasp/auth'
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
|
||||
export function getName(user?: User) {
|
||||
export function getName(user?: AuthUser) {
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
// We use multiple auth methods, so we need to check which one is available.
|
||||
const emailIdentity = findUserIdentity(user, 'email')
|
||||
if (emailIdentity !== undefined) {
|
||||
return getEmail(user)
|
||||
if (user.identities.email !== null) {
|
||||
return user.identities.email.id
|
||||
}
|
||||
|
||||
const googleIdentity = findUserIdentity(user, 'google')
|
||||
if (googleIdentity !== undefined) {
|
||||
return `Google user ${googleIdentity.providerUserId}`
|
||||
if (user.identities.google !== null) {
|
||||
return `Google user ${user.identities.google.id}`
|
||||
}
|
||||
|
||||
const githubIdentity = findUserIdentity(user, 'github')
|
||||
if (githubIdentity !== undefined) {
|
||||
return `GitHub user ${githubIdentity.providerUserId}`
|
||||
if (user.identities.github !== null) {
|
||||
return `GitHub user ${user.identities.github.id}`
|
||||
}
|
||||
|
||||
const keycloakIdentity = findUserIdentity(user, 'keycloak')
|
||||
if (keycloakIdentity) {
|
||||
return `Keycloak user ${keycloakIdentity.providerUserId}`
|
||||
}
|
||||
// if (user.identities.keycloak !== undefined) {
|
||||
// return `Keycloak user ${user.identities.keycloak.id}`
|
||||
// }
|
||||
|
||||
// If we don't know how to get the name, return null.
|
||||
return null
|
||||
}
|
||||
|
||||
export function getProviderData(user?: User) {
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
const emailIdentity = findUserIdentity(user, 'email')
|
||||
return emailIdentity && 'isEmailVerified' in emailIdentity.providerData
|
||||
? emailIdentity.providerData
|
||||
: null
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { getFirstProviderUserId } from "wasp/auth";
|
||||
import { type WebSocketDefinition } from "wasp/server/webSocket";
|
||||
import { type WebSocketDefinition } from 'wasp/server/webSocket'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export const webSocketFn: WebSocketDefinition<
|
||||
@ -8,7 +7,7 @@ export const webSocketFn: WebSocketDefinition<
|
||||
InterServerEvents
|
||||
> = (io, context) => {
|
||||
io.on('connection', (socket) => {
|
||||
const username = getFirstProviderUserId(socket.data.user) ?? 'Unknown'
|
||||
const username = socket.data.user?.getFirstProviderUserId() ?? 'Unknown'
|
||||
console.log('a user connected: ', username)
|
||||
|
||||
socket.on('chatMessage', async (msg) => {
|
||||
|
679
waspc/headless-test/examples/todoApp/package-lock.json
generated
679
waspc/headless-test/examples/todoApp/package-lock.json
generated
@ -24,32 +24,31 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@lucia-auth/adapter-prisma": "^4.0.0-beta.9",
|
||||
"@lucia-auth/adapter-prisma": "^4.0.0",
|
||||
"@prisma/client": "4.16.2",
|
||||
"@stitches/react": "^1.2.8",
|
||||
"@tanstack/react-query": "^4.29.0",
|
||||
"@testing-library/jest-dom": "^6.3.0",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@types/express-serve-static-core": "^4.17.13",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@vitest/ui": "^1.2.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"axios": "^1.4.0",
|
||||
"express": "~4.18.1",
|
||||
"jsdom": "^21.1.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lucia": "^3.0.0-beta.14",
|
||||
"lucia": "^3.0.1",
|
||||
"mitt": "3.0.0",
|
||||
"msw": "^1.1.0",
|
||||
"nodemailer": "^6.9.1",
|
||||
"oslo": "^1.1.2",
|
||||
"pg-boss": "^8.4.2",
|
||||
"postcss": "^8.4.21",
|
||||
"prisma": "4.16.2",
|
||||
"react": "^18.2.0",
|
||||
"react-hook-form": "^7.45.4",
|
||||
"react-router-dom": "^5.3.3",
|
||||
"secure-password": "^4.0.0",
|
||||
"sodium-native": "3.3.0",
|
||||
"superjson": "^1.12.2",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"uuid": "^9.0.0",
|
||||
@ -59,13 +58,491 @@
|
||||
"@tsconfig/node18": "latest"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/node_modules/sodium-native": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-3.3.0.tgz",
|
||||
"integrity": "sha512-rg6lCDM/qa3p07YGqaVD+ciAbUqm6SoO4xmlcfkbU5r1zIGrguXztLiEtaLYTV5U6k8KSIUFmnU3yQUSKmf6DA==",
|
||||
"hasInstallScript": true,
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
"@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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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"
|
||||
}
|
||||
},
|
||||
".wasp/out/sdk/wasp/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/@adobe/css-tools": {
|
||||
@ -1775,6 +2252,11 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/history": {
|
||||
"version": "4.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
||||
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA=="
|
||||
},
|
||||
"node_modules/@types/http-errors": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
|
||||
@ -1837,6 +2319,25 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-router": {
|
||||
"version": "5.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
|
||||
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
|
||||
"dependencies": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-router-dom": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
|
||||
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
|
||||
"dependencies": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||
@ -2421,11 +2922,6 @@
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||
},
|
||||
"node_modules/buffer-writer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||
@ -3047,14 +3543,6 @@
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -3527,6 +4015,12 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-monkey": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz",
|
||||
"integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@ -4444,51 +4938,6 @@
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
|
||||
"integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA=="
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "8.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
|
||||
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4",
|
||||
"npm": ">=1.4.28"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||
@ -4532,46 +4981,16 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"node_modules/log-symbols": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||
@ -4686,6 +5105,27 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/memfs": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz",
|
||||
"integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fs-monkey": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/memfs-browser": {
|
||||
"version": "3.5.10302",
|
||||
"resolved": "https://registry.npmjs.org/memfs-browser/-/memfs-browser-3.5.10302.tgz",
|
||||
"integrity": "sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"memfs": "3.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
@ -4931,11 +5371,6 @@
|
||||
"thenify-all": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoassert": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz",
|
||||
"integrity": "sha512-C40jQ3NzfkP53NsO8kEOFd79p4b9kDXQMwgiY1z8ZwrDZgUyom0AHwGegF4Dm99L+YoYhuaB0ceerUcXmqr1rQ=="
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
@ -4999,16 +5434,6 @@
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
|
||||
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
@ -6154,23 +6579,6 @@
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/secure-password": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-password/-/secure-password-4.0.0.tgz",
|
||||
"integrity": "sha512-B268T/tx+hq7q85KH6gonEqK/lhrLhNtzYzqojuMtBPVFBtwiIwxqF+4yr9POsJu5cIxbJyM66eYfXZiPZUXRA==",
|
||||
"dependencies": {
|
||||
"nanoassert": "^1.0.0",
|
||||
"sodium-native": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
@ -6342,15 +6750,6 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/sodium-native": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-3.4.1.tgz",
|
||||
"integrity": "sha512-PaNN/roiFWzVVTL6OqjzYct38NSXewdl2wz8SRB51Br/MLIJPrbM3XexhVWkq7D3UWMysfrhKVf1v1phZq6MeQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { type AuthUser as User } from "wasp/auth";
|
||||
import { Link } from "wasp/client/router";
|
||||
import { api } from "wasp/client/api";
|
||||
import { getName, getProviderData } from '../user'
|
||||
import { type AuthUser as User } from 'wasp/auth'
|
||||
import { Link } from 'wasp/client/router'
|
||||
import { api } from 'wasp/client/api'
|
||||
import { getName } from '../user'
|
||||
|
||||
async function fetchCustomRoute() {
|
||||
const res = await api.get('/foo/bar')
|
||||
@ -15,7 +15,6 @@ export const ProfilePage = ({ user }: { user: User }) => {
|
||||
}, [])
|
||||
|
||||
const name = getName(user)
|
||||
const providerData = getProviderData(user)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -23,7 +22,7 @@ export const ProfilePage = ({ user }: { user: User }) => {
|
||||
<div>
|
||||
Hello <strong>{name}</strong>! Your status is{' '}
|
||||
<strong>
|
||||
{providerData && providerData.isEmailVerified
|
||||
{user.identities.email && user.identities.email.isEmailVerified
|
||||
? 'verfied'
|
||||
: 'unverified'}
|
||||
</strong>
|
||||
|
@ -1,32 +1,19 @@
|
||||
import { getEmail, findUserIdentity, type AuthUser as User } from "wasp/auth";
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
|
||||
export function getName(user?: User) {
|
||||
export function getName(user?: AuthUser) {
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
// We use multiple auth methods, so we need to check which one is available.
|
||||
const emailIdentity = findUserIdentity(user, 'email')
|
||||
if (emailIdentity) {
|
||||
return getEmail(user)
|
||||
if (user.identities.email !== null) {
|
||||
return user.identities.email.id
|
||||
}
|
||||
|
||||
const googleIdentity = findUserIdentity(user, 'google')
|
||||
if (googleIdentity) {
|
||||
return `Google user ${googleIdentity.providerUserId}`
|
||||
if (user.identities.google !== null) {
|
||||
return `Google user ${user.identities.google.id}`
|
||||
}
|
||||
|
||||
// If we don't know how to get the name, return null.
|
||||
return null
|
||||
}
|
||||
|
||||
export function getProviderData(user?: User) {
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
const emailIdentity = findUserIdentity(user, 'email')
|
||||
return emailIdentity && 'isEmailVerified' in emailIdentity.providerData
|
||||
? emailIdentity.providerData
|
||||
: null
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { getFirstProviderUserId } from "wasp/auth";
|
||||
import { type MiddlewareConfigFn } from "wasp/server";
|
||||
import { type BarBaz, type FooBar, type WebhookCallback } from "wasp/server/api";
|
||||
import { type MiddlewareConfigFn } from 'wasp/server'
|
||||
import { type BarBaz, type FooBar, type WebhookCallback } from 'wasp/server/api'
|
||||
import express from 'express'
|
||||
|
||||
export const fooBar: FooBar = (_req, res, context) => {
|
||||
const username = getFirstProviderUserId(context?.user) ?? 'Anonymous'
|
||||
const username = context?.user?.getFirstProviderUserId() ?? 'Anonymous'
|
||||
res.json({ msg: `Hello, ${username}!` })
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,6 @@ generateLayoutComponent newProjectDetails =
|
||||
[trimming|
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAuth, logout } from "wasp/client/auth";
|
||||
import { getUsername } from "wasp/auth";
|
||||
import "./Main.css";
|
||||
|
||||
export const Layout = ({ children }) => {
|
||||
@ -262,7 +261,7 @@ generateLayoutComponent newProjectDetails =
|
||||
</Link>
|
||||
{ user ? (
|
||||
<span>
|
||||
Hi, {getUsername(user)}!{' '}
|
||||
Hi, {user.identities.username?.id}!{' '}
|
||||
<button onClick={logout} className="text-xl2 underline">
|
||||
(Log out)
|
||||
</button>
|
||||
|
@ -1,6 +1,9 @@
|
||||
module Wasp.Generator.AuthProviders where
|
||||
|
||||
import Data.Aeson (KeyValue ((.=)), object)
|
||||
import qualified Data.Aeson as Aeson
|
||||
import Data.Maybe (fromJust)
|
||||
import qualified Wasp.AppSpec.App.Auth as AS.Auth
|
||||
import Wasp.Generator.AuthProviders.Common (makeProviderId)
|
||||
import qualified Wasp.Generator.AuthProviders.Email as E
|
||||
import qualified Wasp.Generator.AuthProviders.Local as L
|
||||
@ -43,3 +46,13 @@ emailAuthProvider =
|
||||
{ E._providerId = fromJust $ makeProviderId "email",
|
||||
E._displayName = "Email and password"
|
||||
}
|
||||
|
||||
getEnabledAuthProvidersJson :: AS.Auth.Auth -> Aeson.Value
|
||||
getEnabledAuthProvidersJson auth =
|
||||
object
|
||||
[ "isGoogleAuthEnabled" .= AS.Auth.isGoogleAuthEnabled auth,
|
||||
"isKeycloakAuthEnabled" .= AS.Auth.isKeycloakAuthEnabled auth,
|
||||
"isGitHubAuthEnabled" .= AS.Auth.isGitHubAuthEnabled auth,
|
||||
"isUsernameAndPasswordAuthEnabled" .= AS.Auth.isUsernameAndPasswordAuthEnabled auth,
|
||||
"isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth
|
||||
]
|
||||
|
@ -156,11 +156,7 @@ genEntitiesAndServerTypesDirs spec =
|
||||
object
|
||||
[ "entities" .= allEntities,
|
||||
"isAuthEnabled" .= isJust maybeUserEntityName,
|
||||
"userEntityName" .= userEntityName,
|
||||
"authEntityName" .= DbAuth.authEntityName,
|
||||
"authFieldOnUserEntityName" .= DbAuth.authFieldOnUserEntityName,
|
||||
"authIdentityEntityName" .= DbAuth.authIdentityEntityName,
|
||||
"identitiesFieldOnAuthEntityName" .= DbAuth.identitiesFieldOnAuthEntityName,
|
||||
"userFieldName" .= toLowerFirst userEntityName
|
||||
]
|
||||
)
|
||||
|
@ -11,6 +11,7 @@ import Wasp.Generator.AuthProviders
|
||||
googleAuthProvider,
|
||||
keycloakAuthProvider,
|
||||
)
|
||||
import qualified Wasp.Generator.AuthProviders as AuthProviders
|
||||
import qualified Wasp.Generator.AuthProviders.OAuth as OAuth
|
||||
import Wasp.Generator.FileDraft (FileDraft)
|
||||
import Wasp.Generator.Monad (Generator)
|
||||
@ -115,19 +116,10 @@ genLoginSignupForm auth =
|
||||
"areBothSocialAndPasswordBasedAuthEnabled" .= areBothSocialAndPasswordBasedAuthEnabled,
|
||||
"isAnyPasswordBasedAuthEnabled" .= isAnyPasswordBasedAuthEnabled,
|
||||
"isSocialAuthEnabled" .= AS.Auth.isExternalAuthEnabled auth,
|
||||
-- Google
|
||||
"isGoogleAuthEnabled" .= AS.Auth.isGoogleAuthEnabled auth,
|
||||
"googleSignInPath" .= OAuth.serverLoginUrl googleAuthProvider,
|
||||
-- Keycloak
|
||||
"isKeycloakAuthEnabled" .= AS.Auth.isKeycloakAuthEnabled auth,
|
||||
"keycloakSignInPath" .= OAuth.serverLoginUrl keycloakAuthProvider,
|
||||
-- GitHub
|
||||
"isGitHubAuthEnabled" .= AS.Auth.isGitHubAuthEnabled auth,
|
||||
"gitHubSignInPath" .= OAuth.serverLoginUrl gitHubAuthProvider,
|
||||
-- Username and password
|
||||
"isUsernameAndPasswordAuthEnabled" .= AS.Auth.isUsernameAndPasswordAuthEnabled auth,
|
||||
-- Email
|
||||
"isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth
|
||||
"enabledProviders" .= AuthProviders.getEnabledAuthProvidersJson auth
|
||||
]
|
||||
areBothSocialAndPasswordBasedAuthEnabled = AS.Auth.isExternalAuthEnabled auth && isAnyPasswordBasedAuthEnabled
|
||||
isAnyPasswordBasedAuthEnabled = AS.Auth.isUsernameAndPasswordAuthEnabled auth || AS.Auth.isEmailAuthEnabled auth
|
||||
|
@ -29,7 +29,9 @@ genAuth spec =
|
||||
Nothing -> return []
|
||||
Just auth ->
|
||||
-- shared stuff
|
||||
sequence [genFileCopy [relfile|auth/user.ts|]]
|
||||
sequence
|
||||
[ genUserTs
|
||||
]
|
||||
-- client stuff
|
||||
<++> sequence
|
||||
[ genFileCopy [relfile|auth/helpers/user.ts|],
|
||||
@ -131,3 +133,8 @@ genProvidersTypes auth = return $ C.mkTmplFdWithData [relfile|auth/providers/typ
|
||||
userEntityName = AS.refName $ AS.Auth.userEntity auth
|
||||
|
||||
tmplData = object ["userEntityUpper" .= (userEntityName :: String)]
|
||||
|
||||
genUserTs :: Generator FileDraft
|
||||
genUserTs = return $ C.mkTmplFdWithData [relfile|auth/user.ts|] tmplData
|
||||
where
|
||||
tmplData = object ["authIdentityEntityName" .= DbAuth.authIdentityEntityName]
|
||||
|
@ -3,13 +3,12 @@ module Wasp.Generator.SdkGenerator.Client.AuthG
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (object, (.=))
|
||||
import qualified Data.Aeson as Aeson
|
||||
import StrongPath (File', Path', Rel, relfile)
|
||||
import Wasp.AppSpec (AppSpec)
|
||||
import qualified Wasp.AppSpec.App as AS.App
|
||||
import qualified Wasp.AppSpec.App.Auth as AS.Auth
|
||||
import Wasp.AppSpec.Valid (getApp)
|
||||
import qualified Wasp.Generator.AuthProviders as AuthProviders
|
||||
import Wasp.Generator.FileDraft (FileDraft)
|
||||
import Wasp.Generator.Monad (Generator)
|
||||
import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir)
|
||||
@ -40,7 +39,7 @@ genAuthIndex auth =
|
||||
[relfile|client/auth/index.ts|]
|
||||
tmplData
|
||||
where
|
||||
tmplData = getAuthProvidersJson auth
|
||||
tmplData = AuthProviders.getEnabledAuthProvidersJson auth
|
||||
|
||||
genAuthUI :: AS.Auth.Auth -> Generator FileDraft
|
||||
genAuthUI auth =
|
||||
@ -49,7 +48,7 @@ genAuthUI auth =
|
||||
[relfile|client/auth/ui.ts|]
|
||||
tmplData
|
||||
where
|
||||
tmplData = getAuthProvidersJson auth
|
||||
tmplData = AuthProviders.getEnabledAuthProvidersJson auth
|
||||
|
||||
genAuthEmail :: AS.Auth.Auth -> Generator [FileDraft]
|
||||
genAuthEmail auth =
|
||||
@ -81,15 +80,5 @@ genAuthGitHub auth =
|
||||
then sequence [genFileCopy [relfile|client/auth/github.ts|]]
|
||||
else return []
|
||||
|
||||
getAuthProvidersJson :: AS.Auth.Auth -> Aeson.Value
|
||||
getAuthProvidersJson auth =
|
||||
object
|
||||
[ "isGoogleAuthEnabled" .= AS.Auth.isGoogleAuthEnabled auth,
|
||||
"isKeycloakAuthEnabled" .= AS.Auth.isKeycloakAuthEnabled auth,
|
||||
"isGitHubAuthEnabled" .= AS.Auth.isGitHubAuthEnabled auth,
|
||||
"isUsernameAndPasswordAuthEnabled" .= AS.Auth.isUsernameAndPasswordAuthEnabled auth,
|
||||
"isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth
|
||||
]
|
||||
|
||||
genFileCopy :: Path' (Rel SdkTemplatesDir) File' -> Generator FileDraft
|
||||
genFileCopy = return . C.mkTmplFd
|
||||
|
@ -4,12 +4,14 @@ module Wasp.Generator.SdkGenerator.Server.AuthG
|
||||
where
|
||||
|
||||
import Data.Aeson (object, (.=))
|
||||
import qualified Data.Aeson as Aeson
|
||||
import StrongPath (File', Path', Rel, relfile)
|
||||
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.Auth
|
||||
import Wasp.AppSpec.Valid (getApp)
|
||||
import qualified Wasp.Generator.AuthProviders as AuthProviders
|
||||
import qualified Wasp.Generator.DbGenerator.Auth as DbAuth
|
||||
import Wasp.Generator.FileDraft (FileDraft)
|
||||
import Wasp.Generator.Monad (Generator)
|
||||
import Wasp.Generator.SdkGenerator.Common (SdkTemplatesDir)
|
||||
@ -22,7 +24,8 @@ genNewServerApi spec =
|
||||
Nothing -> return []
|
||||
Just auth ->
|
||||
sequence
|
||||
[ genAuthIndex auth
|
||||
[ genAuthIndex auth,
|
||||
genAuthUser auth
|
||||
]
|
||||
<++> genAuthEmail auth
|
||||
<++> genAuthUsername auth
|
||||
@ -36,7 +39,26 @@ genAuthIndex auth =
|
||||
[relfile|server/auth/index.ts|]
|
||||
tmplData
|
||||
where
|
||||
tmplData = getAuthProvidersJson auth
|
||||
tmplData = AuthProviders.getEnabledAuthProvidersJson auth
|
||||
|
||||
genAuthUser :: AS.Auth.Auth -> Generator FileDraft
|
||||
genAuthUser auth =
|
||||
return $
|
||||
C.mkTmplFdWithData
|
||||
[relfile|server/auth/user.ts|]
|
||||
tmplData
|
||||
where
|
||||
userEntityName = AS.refName $ AS.Auth.userEntity auth
|
||||
|
||||
tmplData =
|
||||
object
|
||||
[ "userEntityName" .= userEntityName,
|
||||
"authEntityName" .= DbAuth.authEntityName,
|
||||
"authFieldOnUserEntityName" .= DbAuth.authFieldOnUserEntityName,
|
||||
"authIdentityEntityName" .= DbAuth.authIdentityEntityName,
|
||||
"identitiesFieldOnAuthEntityName" .= DbAuth.identitiesFieldOnAuthEntityName,
|
||||
"enabledProviders" .= AuthProviders.getEnabledAuthProvidersJson auth
|
||||
]
|
||||
|
||||
genAuthEmail :: AS.Auth.Auth -> Generator [FileDraft]
|
||||
genAuthEmail auth =
|
||||
@ -50,15 +72,5 @@ genAuthUsername auth =
|
||||
then sequence [genFileCopy [relfile|server/auth/username.ts|]]
|
||||
else return []
|
||||
|
||||
getAuthProvidersJson :: AS.Auth.Auth -> Aeson.Value
|
||||
getAuthProvidersJson auth =
|
||||
object
|
||||
[ "isGoogleAuthEnabled" .= AS.Auth.isGoogleAuthEnabled auth,
|
||||
"isKeycloakAuthEnabled" .= AS.Auth.isKeycloakAuthEnabled auth,
|
||||
"isGitHubAuthEnabled" .= AS.Auth.isGitHubAuthEnabled auth,
|
||||
"isUsernameAndPasswordAuthEnabled" .= AS.Auth.isUsernameAndPasswordAuthEnabled auth,
|
||||
"isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth
|
||||
]
|
||||
|
||||
genFileCopy :: Path' (Rel SdkTemplatesDir) File' -> Generator FileDraft
|
||||
genFileCopy = return . C.mkTmplFd
|
||||
|
7
web/docs/auth/_accessing-user-data-note.md
Normal file
7
web/docs/auth/_accessing-user-data-note.md
Normal file
@ -0,0 +1,7 @@
|
||||
<small>
|
||||
|
||||
Read more about accessing the user data in the [Accessing User Data](/auth/entities/entities.md#accessing-the-auth-fields) section of the docs.
|
||||
|
||||
</small>
|
||||
|
||||
<!-- This snippet is used in all of the individual auth provider docs. -->
|
@ -1 +1,5 @@
|
||||
You can read more about how the `User` entity is connected to the rest of the auth system in the [Auth Entities](./entities) section of the docs.
|
||||
<small>
|
||||
|
||||
You can read more about how the `User` is connected to the rest of the auth system and how you can access the user data in the [Accessing User Data](./entities) section of the docs.
|
||||
|
||||
</small>
|
@ -5,9 +5,10 @@ title: Email
|
||||
import { Required } from '@site/src/components/Tag';
|
||||
import MultipleIdentitiesWarning from './\_multiple-identities-warning.md';
|
||||
import ReadMoreAboutAuthEntities from './\_read-more-about-auth-entities.md';
|
||||
import GetEmail from './entities/\_get-email.md';
|
||||
import UserSignupFieldsExplainer from './\_user-signup-fields-explainer.md';
|
||||
import UserFields from './\_user-fields.md';
|
||||
import EmailData from './entities/\_email-data.md';
|
||||
import AccessingUserDataNote from './\_accessing-user-data-note.md';
|
||||
|
||||
Wasp supports e-mail authentication out of the box, along with email verification and "forgot your password?" flows. It provides you with the server-side implementation and email templates for all of these flows.
|
||||
|
||||
@ -814,13 +815,11 @@ We suggest using the built-in field validators for your authentication flow. You
|
||||
|
||||
To read more about how to set up the logout button and how to get access to the logged-in user in our client and server code, read the [auth overview docs](../auth/overview).
|
||||
|
||||
### `getEmail`
|
||||
When you receive the `user` object [on the client or the server](./overview.md#accessing-the-logged-in-user), you'll be able to access the user's email and other information like this:
|
||||
|
||||
If you are looking to access the user's email in your code, you can do that by accessing the info about the user that is stored in the `user.auth.identities` array.
|
||||
<EmailData />
|
||||
|
||||
To make things a bit easier for you, Wasp offers the `getEmail` helper.
|
||||
|
||||
<GetEmail />
|
||||
<AccessingUserDataNote />
|
||||
|
||||
## API Reference
|
||||
|
||||
|
15
web/docs/auth/entities/_email-data.md
Normal file
15
web/docs/auth/entities/_email-data.md
Normal file
@ -0,0 +1,15 @@
|
||||
```ts
|
||||
const emailIdentity = user.identities.email
|
||||
|
||||
// Email address the the user used to sign up, e.g. "fluffyllama@app.com".
|
||||
emailIdentity.id
|
||||
|
||||
// `true` if the user has verified their email address.
|
||||
emailIdentity.isEmailVerified
|
||||
|
||||
// Datetime when the email verification email was sent.
|
||||
emailIdentity.emailVerificationSentAt
|
||||
|
||||
// Datetime when the last password reset email was sent.
|
||||
emailIdentity.passwordResetSentAt
|
||||
```
|
@ -1,47 +0,0 @@
|
||||
The `getEmail` helper returns the user's email or `null` if the user doesn't have an email auth identity.
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
import { getEmail } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }) => {
|
||||
const email = getEmail(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```js title=src/tasks.js
|
||||
import { getEmail } from 'wasp/auth'
|
||||
|
||||
export const createTask = async (args, context) => {
|
||||
const email = getEmail(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getEmail, AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const email = getEmail(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
import { getEmail } from 'wasp/auth'
|
||||
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const email = getEmail(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
@ -1,47 +0,0 @@
|
||||
The `getUsername` helper returns the user's username or `null` if the user doesn't have a username auth identity.
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
import { getUsername } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }) => {
|
||||
const username = getUsername(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```js title=src/tasks.js
|
||||
import { getUsername } from 'wasp/auth'
|
||||
|
||||
export const createTask = async (args, context) => {
|
||||
const username = getUsername(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getUsername, AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const username = getUsername(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
import { getUsername } from 'wasp/auth'
|
||||
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const username = getUsername(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
6
web/docs/auth/entities/_github-data.md
Normal file
6
web/docs/auth/entities/_github-data.md
Normal file
@ -0,0 +1,6 @@
|
||||
```ts
|
||||
const githubIdentity = user.identities.github
|
||||
|
||||
// GitHub User ID for example "12345678"
|
||||
githubIdentity.id
|
||||
```
|
6
web/docs/auth/entities/_google-data.md
Normal file
6
web/docs/auth/entities/_google-data.md
Normal file
@ -0,0 +1,6 @@
|
||||
```ts
|
||||
const googleIdentity = user.identities.google
|
||||
|
||||
// Google User ID for example "123456789012345678901"
|
||||
googleIdentity.id
|
||||
```
|
6
web/docs/auth/entities/_keycloak-data.md
Normal file
6
web/docs/auth/entities/_keycloak-data.md
Normal file
@ -0,0 +1,6 @@
|
||||
```ts
|
||||
const keycloakIdentity = user.identities.keycloak
|
||||
|
||||
// Keycloak User ID for example "12345678-1234-1234-1234-123456789012"
|
||||
keycloakIdentity.id
|
||||
```
|
6
web/docs/auth/entities/_username-data.md
Normal file
6
web/docs/auth/entities/_username-data.md
Normal file
@ -0,0 +1,6 @@
|
||||
```ts
|
||||
const usernameIdentity = user.identities.username
|
||||
|
||||
// Username that the user used to sign up, e.g. "fluffyllama"
|
||||
usernameIdentity.id
|
||||
```
|
@ -1,14 +1,454 @@
|
||||
---
|
||||
title: Auth Entities
|
||||
title: Accessing User Data
|
||||
---
|
||||
|
||||
import ImgWithCaption from '@site/blog/components/ImgWithCaption'
|
||||
import { Internal } from '@site/src/components/Tag'
|
||||
import MultipleIdentitiesWarning from '../\_multiple-identities-warning.md';
|
||||
import GetEmail from './\_get-email.md';
|
||||
import GetUsername from './\_get-username.md';
|
||||
import UsernameData from './\_username-data.md';
|
||||
import EmailData from './\_email-data.md';
|
||||
import GoogleData from './\_google-data.md';
|
||||
import GithubData from './\_github-data.md';
|
||||
import KeycloakData from './\_keycloak-data.md';
|
||||
|
||||
Wasp supports multiple different authentication methods and for each method, we need to store different information about the user. For example, if you are using the [Username & password](./username-and-pass) authentication method, we need to store the user's username and password. On the other hand, if you are using the [Email](./email) authentication method, you will need to store the user's email, password and for example, their email verification status.
|
||||
First, we'll check out the most practical info: **how to access the user's data in your app**.
|
||||
|
||||
Then, we'll dive into the details of the **auth entities** that Wasp creates behind the scenes to store the user's data. For auth each method, Wasp needs to store different information about the user. For example, username for [Username & password](./username-and-pass) auth, email verification status for [Email](./email) auth, and so on.
|
||||
|
||||
We'll also show you how you can use these entities to create a custom signup action.
|
||||
|
||||
## Accessing the Auth Fields
|
||||
|
||||
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), it will contain all the user fields you defined in the `User` entity in your Wasp file. In addition to that, it will also contain all the auth-related fields that Wasp stores. This includes things like the `username` or the email verification status. In Wasp, this data is called the `AuthUser` object.
|
||||
|
||||
### `AuthUser` Object Fields
|
||||
|
||||
All the `User` fields you defined will be present at the top level of the `AuthUser` object. The auth-related fields will be on the `identities` object. For each auth method you enable, there will be a separate data object in the `identities` object.
|
||||
|
||||
The `AuthUser` object will change depending on which auth method you have enabled in the Wasp file. For example, if you enabled the email auth and Google auth, it would look something like this:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="google" label="User Signed Up with Google">
|
||||
|
||||
If the user has only the Google identity, the `AuthUser` object will look like this:
|
||||
|
||||
```ts
|
||||
const user = {
|
||||
// User data
|
||||
id: 'cluqs9qyh00007cn73apj4hp7',
|
||||
address: 'Some address',
|
||||
|
||||
// Auth methods specific data
|
||||
identities: {
|
||||
email: null,
|
||||
google: {
|
||||
id: '1117XXXX1301972049448',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="email" label="User Signed Up with Email">
|
||||
|
||||
If the user has only the email identity, the `AuthUser` object will look like this:
|
||||
|
||||
```ts
|
||||
const user = {
|
||||
// User data
|
||||
id: 'cluqsex9500017cn7i2hwsg17',
|
||||
address: 'Some address',
|
||||
|
||||
// Auth methods specific data
|
||||
identities: {
|
||||
email: {
|
||||
id: 'user@app.com',
|
||||
isEmailVerified: true,
|
||||
emailVerificationSentAt: '2024-04-08T10:06:02.204Z',
|
||||
passwordResetSentAt: null,
|
||||
},
|
||||
google: null,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
In the examples above, you can see the `identities` object contains the `email` and `google` objects. The `email` object contains the email-related data and the `google` object contains the Google-related data.
|
||||
|
||||
:::info Make sure to check if the data exists
|
||||
|
||||
Before accessing some auth method's data, you'll need to check if that data exists for the user and then access it:
|
||||
|
||||
```ts
|
||||
if (user.identities.google !== null) {
|
||||
const userId = user.identities.google.id
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You need to do this because if a user didn't sign up with some auth method, the data for that auth method will be `null`.
|
||||
:::
|
||||
|
||||
Let's look at the data for each of the available auth methods:
|
||||
|
||||
- [Username & password](../username-and-pass.md) data
|
||||
|
||||
<UsernameData />
|
||||
|
||||
- [Email](../email.md) data
|
||||
|
||||
<EmailData />
|
||||
|
||||
- [Google](../social-auth/google.md) data
|
||||
|
||||
<GoogleData />
|
||||
|
||||
- [GitHub](../social-auth/github.md) data
|
||||
|
||||
<GithubData />
|
||||
|
||||
- [Keycloak](../social-auth/keycloak.md) data
|
||||
|
||||
<KeycloakData />
|
||||
|
||||
If you support multiple auth methods, you'll need to find which identity exists for the user and then access its data:
|
||||
|
||||
```ts
|
||||
if (user.identities.email !== null) {
|
||||
const email = user.identities.email.id
|
||||
// ...
|
||||
} else if (user.identities.google !== null) {
|
||||
const googleId = user.identities.google.id
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### `getFirstProviderUserId` Helper
|
||||
|
||||
The `getFirstProviderUserId` method returns the first user ID that it finds for the user. For example if the user has signed up with email, it will return the email. If the user has signed up with Google, it will return the Google ID.
|
||||
|
||||
This can be useful if you support multiple authentication methods and you need _any_ ID that identifies the user in your app.
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
const MainPage = ({ user }) => {
|
||||
const userId = user.getFirstProviderUserId()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```js title=src/tasks.js
|
||||
export const createTask = async (args, context) => {
|
||||
const userId = context.user.getFirstProviderUserId()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const userId = user.getFirstProviderUserId()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const userId = context.user.getFirstProviderUserId()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<small>
|
||||
|
||||
\* Multiple identities per user will be possible in the future and then the `getFirstProviderUserId` method will return the ID of the first identity that it finds without any guarantees about which one it will be.
|
||||
|
||||
</small>
|
||||
|
||||
## Including the User with Other Entities
|
||||
|
||||
Sometimes, you might want to include the user's data when fetching other entities. For example, you might want to include the user's data with the tasks they have created.
|
||||
|
||||
We'll mention the `auth` and the `identities` relations which we will explain in more detail later in the [Entities Explained](#entities-explained) section.
|
||||
|
||||
:::caution Be careful about sensitive data
|
||||
|
||||
You'll need to include the `auth` and the `identities` relations to get the full auth data about the user. However, you should keep in mind that the `providerData` field in the `identities` can contain sensitive data like the user's hashed password (in case of email or username auth), so you will likely want to exclude it if you are returning those values to the client.
|
||||
|
||||
:::
|
||||
|
||||
You can include the full user's data with other entities using the `include` option in the Prisma queries:
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```js title="src/tasks.js"
|
||||
export const getAllTasks = async (args, context) => {
|
||||
return context.entities.Task.findMany({
|
||||
orderBy: { id: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
// highlight-next-line
|
||||
user: {
|
||||
include: {
|
||||
// highlight-next-line
|
||||
auth: {
|
||||
include: {
|
||||
// highlight-next-line
|
||||
identities: {
|
||||
// Including only the `providerName` and `providerUserId` fields
|
||||
select: {
|
||||
providerName: true,
|
||||
providerUserId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```ts title="src/tasks.ts"
|
||||
export const getAllTasks = (async (args, context) => {
|
||||
return context.entities.Task.findMany({
|
||||
orderBy: { id: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
// highlight-next-line
|
||||
user: {
|
||||
include: {
|
||||
// highlight-next-line
|
||||
auth: {
|
||||
include: {
|
||||
// highlight-next-line
|
||||
identities: {
|
||||
// Including only the `providerName` and `providerUserId` fields
|
||||
select: {
|
||||
providerName: true,
|
||||
providerUserId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}) satisfies tasks.GetAllQuery<{}, {}>
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
If you have some **piece of the auth data that you want to access frequently** (for example the `username`), it's best to store it at the top level of the `User` entity.
|
||||
|
||||
For example, save the `username` or `email` as a property on the `User` and you'll be able to access it without including the `auth` and `identities` fields. We show an example in the [Defining Extra Fields on the User Entity](../overview.md#1-defining-extra-fields) section of the docs.
|
||||
|
||||
### Getting Auth Data from the User Object
|
||||
|
||||
When you have the `user` object with the `auth` and `identities` fields, it can be a bit tedious to obtain the auth data (like username or Google ID) from it:
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
function MainPage() {
|
||||
// ...
|
||||
return (
|
||||
<div className="tasks">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="task">
|
||||
{task.title} by {task.user.auth?.identities[0].providerUserId}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
function MainPage() {
|
||||
// ...
|
||||
return (
|
||||
<div className="tasks">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="task">
|
||||
{task.title} by {task.user.auth?.identities[0].providerUserId}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Wasp offers a few helper methods to access the user's auth data when you retrieve the `user` like this. They are `getUsername`, `getEmail` and `getFirstProviderUserId`. They can be used both on the client and the server.
|
||||
|
||||
#### `getUsername`
|
||||
|
||||
It accepts the `user` object and if the user signed up with the [Username & password](./username-and-pass) auth method, it returns the username or `null` otherwise. The `user` object needs to have the `auth` and the `identities` relations included.
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
import { getUsername } from 'wasp/auth'
|
||||
|
||||
function MainPage() {
|
||||
// ...
|
||||
return (
|
||||
<div className="tasks">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="task">
|
||||
{task.title} by {getUsername(task.user)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getUsername } from 'wasp/auth'
|
||||
|
||||
function MainPage() {
|
||||
// ...
|
||||
return (
|
||||
<div className="tasks">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="task">
|
||||
{task.title} by {getUsername(task.user)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### `getEmail`
|
||||
|
||||
It accepts the `user` object and if the user signed up with the [Email](./email) auth method, it returns the email or `null` otherwise. The `user` object needs to have the `auth` and the `identities` relations included.
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
import { getEmail } from 'wasp/auth'
|
||||
|
||||
function MainPage() {
|
||||
// ...
|
||||
return (
|
||||
<div className="tasks">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="task">
|
||||
{task.title} by {getEmail(task.user)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getEmail } from 'wasp/auth'
|
||||
|
||||
function MainPage() {
|
||||
// ...
|
||||
return (
|
||||
<div className="tasks">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="task">
|
||||
{task.title} by {getEmail(task.user)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### `getFirstProviderUserId`
|
||||
|
||||
It returns the first user ID that it finds for the user. For example if the user has signed up with email, it will return the email. If the user has signed up with Google, it will return the Google ID. The `user` object needs to have the `auth` and the `identities` relations included.
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
import { getFirstProviderUserId } from 'wasp/auth'
|
||||
|
||||
function MainPage() {
|
||||
// ...
|
||||
return (
|
||||
<div className="tasks">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="task">
|
||||
{task.title} by {getFirstProviderUserId(task.user)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getFirstProviderUserId } from 'wasp/auth'
|
||||
|
||||
function MainPage() {
|
||||
// ...
|
||||
return (
|
||||
<div className="tasks">
|
||||
{tasks.map((task) => (
|
||||
<div key={task.id} className="task">
|
||||
{task.title} by {getFirstProviderUserId(task.user)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Entities Explained
|
||||
|
||||
@ -16,7 +456,7 @@ To store user information, Wasp creates a few entities behind the scenes. In thi
|
||||
|
||||
### User Entity
|
||||
|
||||
When you want to add authentication to your app, you need to specify the user entity e.g. `User` in your Wasp file. This entity is a "business logic user" which represents a user of your app.
|
||||
When you want to add authentication to your app, you need to specify the user entity e.g. `User` in your Wasp file. This entity is a "business logic user" which represents a user of your app.
|
||||
|
||||
You can use this entity to store any information about the user that you want to store. For example, you might want to store the user's name or address. You can also use the user entity to define the relations between users and other entities in your app. For example, you might want to define a relation between a user and the tasks that they have created.
|
||||
|
||||
@ -36,16 +476,18 @@ On the other hand, the `Auth`, `AuthIdentity` and `Session` entities are created
|
||||
In the case you want to create a custom signup action, you will need to use the `Auth` and `AuthIdentity` entities directly.
|
||||
|
||||
### Example App Model
|
||||
|
||||
Let's imagine we created a simple tasks management app:
|
||||
|
||||
- The app has email and Google-based auth.
|
||||
- Users can create tasks and see the tasks that they have created.
|
||||
- The app has email and Google-based auth.
|
||||
- Users can create tasks and see the tasks that they have created.
|
||||
|
||||
Let's look at how would that look in the database:
|
||||
|
||||
<ImgWithCaption alt="Example of Auth Entities" source="img/auth-entities/model-example.png" caption="Example of Auth Entities"/>
|
||||
|
||||
If we take a look at an example user in the database, we can see:
|
||||
|
||||
- The business logic user, `User` is connected to multiple `Task` entities.
|
||||
- In this example, "Example User" has two tasks.
|
||||
- The `User` is connected to exactly one `Auth` entity.
|
||||
@ -65,7 +507,7 @@ entity Auth {=psl
|
||||
id String @id @default(uuid())
|
||||
userId Int? @unique
|
||||
// Wasp injects this relation on the User entity as well
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
identities AuthIdentity[]
|
||||
sessions Session[]
|
||||
psl=}
|
||||
@ -93,11 +535,12 @@ entity AuthIdentity {=psl
|
||||
authId String
|
||||
auth Auth @relation(fields: [authId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([providerName, providerUserId])
|
||||
@@id([providerName, providerUserId])
|
||||
psl=}
|
||||
```
|
||||
|
||||
The `AuthIdentity` fields:
|
||||
|
||||
- `providerName` is the name of the authentication provider.
|
||||
- For example, `email` or `google`.
|
||||
- `providerUserId` is the user's ID in the authentication provider.
|
||||
@ -125,162 +568,13 @@ psl=}
|
||||
```
|
||||
|
||||
The `Session` fields:
|
||||
|
||||
- `id` is a unique identifier of the `Session` entity.
|
||||
- `expiresAt` is the date when the session expires.
|
||||
- `userId` is a foreign key to the `Auth` entity.
|
||||
- It is used to connect the `Session` entity with the `Auth` entity.
|
||||
- `auth` is a relation to the `Auth` entity.
|
||||
|
||||
## Accessing the Auth Fields
|
||||
|
||||
If you are looking to access the user's email or username in your code, you can do that by accessing the info about the user that is stored in the `AuthIdentity` entity.
|
||||
|
||||
Everywhere where Wasp gives you the `user` object, it also includes the `auth` relation with the `identities` relation. This means that you can access the auth identity info by using the `user.auth.identities` array.
|
||||
|
||||
To make things a bit easier for you, Wasp offers a few helper functions that you can use to access the auth identity info.
|
||||
|
||||
### `getEmail`
|
||||
|
||||
<GetEmail />
|
||||
|
||||
### `getUsername`
|
||||
|
||||
<GetUsername />
|
||||
|
||||
### `getFirstProviderUserId`
|
||||
|
||||
The `getFirstProviderUserId` helper returns the first user ID (e.g. `username` or `email`) that it finds for the user or `null` if it doesn't find any.
|
||||
|
||||
[As mentioned before](#authidentity-entity-), the `providerUserId` field is how providers identify our users. For example, the user's `username` in the case of the username auth or the user's `email` in the case of the email auth. This can be useful if you support multiple authentication methods and you need *any* ID that identifies the user in your app.
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
import { getFirstProviderUserId } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }) => {
|
||||
const userId = getFirstProviderUserId(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```js title=src/tasks.js
|
||||
import { getFirstProviderUserId } from 'wasp/auth'
|
||||
|
||||
export const createTask = async (args, context) => {
|
||||
const userId = getFirstProviderUserId(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getFirstProviderUserId, AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const userId = getFirstProviderUserId(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
import { getFirstProviderUserId } from 'wasp/auth'
|
||||
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const userId = getFirstProviderUserId(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### `findUserIdentity`
|
||||
|
||||
You can find a specific auth identity by using the `findUserIdentity` helper function. This function takes a `user` and a `providerName` and returns the first `providerName` identity that it finds or `null` if it doesn't find any.
|
||||
|
||||
Possible provider names are:
|
||||
- `email`
|
||||
- `username`
|
||||
- `google`
|
||||
- `github`
|
||||
|
||||
This can be useful if you want to check if the user has a specific auth identity. For example, you might want to check if the user has an email auth identity or Google auth identity.
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
||||
```jsx title="src/MainPage.jsx"
|
||||
import { findUserIdentity } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }) => {
|
||||
const emailIdentity = findUserIdentity(user, 'email')
|
||||
const googleIdentity = findUserIdentity(user, 'google')
|
||||
if (emailIdentity) {
|
||||
// ...
|
||||
} else if (googleIdentity) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```js title=src/tasks.js
|
||||
import { findUserIdentity } from 'wasp/client/auth'
|
||||
|
||||
export const createTask = async (args, context) => {
|
||||
const emailIdentity = findUserIdentity(context.user, 'email')
|
||||
const googleIdentity = findUserIdentity(context.user, 'google')
|
||||
if (emailIdentity) {
|
||||
// ...
|
||||
} else if (googleIdentity) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { findUserIdentity, AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const emailIdentity = findUserIdentity(user, 'email')
|
||||
const googleIdentity = findUserIdentity(user, 'google')
|
||||
if (emailIdentity) {
|
||||
// ...
|
||||
} else if (googleIdentity) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
import { findUserIdentity } from 'wasp/client/auth'
|
||||
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const emailIdentity = findUserIdentity(context.user, 'email')
|
||||
const googleIdentity = findUserIdentity(context.user, 'google')
|
||||
if (emailIdentity) {
|
||||
// ...
|
||||
} else if (googleIdentity) {
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Custom Signup Action
|
||||
|
||||
Let's take a look at how you can use the `Auth` and `AuthIdentity` entities to create custom login and signup actions. For example, you might want to create a custom signup action that creates a user in your app and also creates a user in a third-party service.
|
||||
@ -304,7 +598,6 @@ action customSignup {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```js title="src/auth/signup.js"
|
||||
import {
|
||||
createProviderId,
|
||||
@ -326,7 +619,7 @@ export const signup = async (args, { entities: { User } }) => {
|
||||
providerId,
|
||||
providerData,
|
||||
// Any additional data you want to store on the User entity
|
||||
{},
|
||||
{}
|
||||
)
|
||||
|
||||
// This is equivalent to:
|
||||
@ -361,6 +654,7 @@ export const signup = async (args, { entities: { User } }) => {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="ts" label="TypeScript">
|
||||
|
||||
@ -407,7 +701,7 @@ export const signup: CustomSignup<
|
||||
providerId,
|
||||
providerData,
|
||||
// Any additional data you want to store on the User entity
|
||||
{},
|
||||
{}
|
||||
)
|
||||
|
||||
// This is equivalent to:
|
||||
@ -442,7 +736,8 @@ export const signup: CustomSignup<
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
You can use whichever method suits your needs better: either the `createUser` function or Prisma's `User.create` method. The `createUser` function is a bit more convenient to use because it hides some of the complexity. On the other hand, the `User.create` method gives you more control over the data that is stored in the `Auth` and `AuthIdentity` entities.
|
||||
You can use whichever method suits your needs better: either the `createUser` function or Prisma's `User.create` method. The `createUser` function is a bit more convenient to use because it hides some of the complexity. On the other hand, the `User.create` method gives you more control over the data that is stored in the `Auth` and `AuthIdentity` entities.
|
||||
|
@ -153,26 +153,22 @@ const LogoutButton = () => {
|
||||
|
||||
You can get access to the `user` object both on the server and on the client. The `user` object contains the logged-in user's data.
|
||||
|
||||
The `user` object has all the fields that you defined in your `User` entity, plus the `auth` field which contains the auth identities connected to the user. For example, if the user signed up with their email, the `user` object might look something like this:
|
||||
The `user` object has all the fields that you defined in your `User` entity. In addition to that, it will also contain all the auth-related fields that Wasp stores. This includes things like the `username` or the email verification status. For example, if you have a user that signed up using an email and password, the `user` object might look like this:
|
||||
|
||||
```js
|
||||
```ts
|
||||
const user = {
|
||||
id: "19c7d164-b5cb-4dde-a0cc-0daea77cf854",
|
||||
// User data
|
||||
id: "cluqsex9500017cn7i2hwsg17",
|
||||
address: "Some address",
|
||||
|
||||
// Your entity's fields.
|
||||
address: "My address",
|
||||
// ...
|
||||
|
||||
// Auth identities connected to the user.
|
||||
auth: {
|
||||
id: "26ab6f96-ed76-4ee5-9ac3-2fd0bf19711f",
|
||||
identities: [
|
||||
{
|
||||
providerName: "email",
|
||||
providerUserId: "some@email.com",
|
||||
providerData: { ... },
|
||||
},
|
||||
]
|
||||
// Auth methods specific data
|
||||
identities: {
|
||||
email: {
|
||||
id: "user@app.com",
|
||||
isEmailVerified: true,
|
||||
emailVerificationSentAt: "2024-04-08T10:06:02.204Z",
|
||||
passwordResetSentAt: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
@ -946,7 +942,7 @@ A dictionary of auth methods enabled for the app.
|
||||
#### `onAuthFailedRedirectTo: String` <Required />
|
||||
|
||||
The route to which Wasp should redirect unauthenticated user when they try to access a private page (i.e., a page that has `authRequired: true`).
|
||||
Check out these [essentials docs on auth](../tutorial/auth#adding-auth-to-the-project) to see an example of usage.
|
||||
Check out these [essential docs on auth](../tutorial/auth#adding-auth-to-the-project) to see an example of usage.
|
||||
|
||||
#### `onAuthSucceededRedirectTo: String`
|
||||
|
||||
|
@ -7,4 +7,4 @@ The reference shows how to define both.
|
||||
|
||||
For behavior common to all providers, check the general [API Reference](/auth/overview.md#api-reference).
|
||||
|
||||
<!-- This snippet is used in google.md and github.md -->
|
||||
<!-- This snippet is used in {google,github,keycloak}.md -->
|
||||
|
@ -1,3 +1,3 @@
|
||||
When a user **signs in for the first time**, Wasp creates a new user account and links it to the chosen auth provider account for future logins.
|
||||
|
||||
<!-- This snippet is used in overview.md, google.md and github.md -->
|
||||
<!-- This snippet is used in {overview,google,github,keycloak}.md -->
|
||||
|
@ -1,3 +1,3 @@
|
||||
Wasp automatically generates the `defineUserSignupFields` function to help you correctly type your `userSignupFields` object.
|
||||
|
||||
<!-- This snippet is used in overview.md, google.md and github.md -->
|
||||
<!-- This snippet is used in {overview,google,github,keycloak}.md -->
|
||||
|
@ -7,4 +7,4 @@ Wasp also lets you customize the configuration of the providers' settings using
|
||||
|
||||
Let's use this example to show both fields in action:
|
||||
|
||||
<!-- This snippet is used in google.md and github.md -->
|
||||
<!-- This snippet is used in {google,github,keycloak}.md -->
|
||||
|
@ -7,4 +7,4 @@ There are two mechanisms used for overriding the default behavior:
|
||||
|
||||
Let's explore them in more detail.
|
||||
|
||||
<!-- This snippet is used in google.md and github.md -->
|
||||
<!-- This snippet is used in {google,github,keycloak}.md -->
|
||||
|
@ -1,3 +1,3 @@
|
||||
To read more about how to set up the logout button and get access to the logged-in user in both client and server code, read the docs on [using auth](../../auth/overview).
|
||||
|
||||
<!-- This snippet is used in google.md and github.md -->
|
||||
<!-- This snippet is used in {google,github,keycloak}.md -->
|
@ -11,6 +11,8 @@ import WaspFileStructureNote from './\_wasp-file-structure-note.md';
|
||||
import GetUserFieldsType from './\_getuserfields-type.md';
|
||||
import ApiReferenceIntro from './\_api-reference-intro.md';
|
||||
import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md';
|
||||
import GithubData from '../entities/_github-data.md';
|
||||
import AccessingUserDataNote from '../\_accessing-user-data-note.md';
|
||||
|
||||
Wasp supports Github Authentication out of the box.
|
||||
GitHub is a great external auth choice when you're building apps for developers, as most of them already have a GitHub account.
|
||||
@ -469,6 +471,12 @@ export function getConfig() {
|
||||
|
||||
<UsingAuthNote />
|
||||
|
||||
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's GitHub ID like this:
|
||||
|
||||
<GithubData />
|
||||
|
||||
<AccessingUserDataNote />
|
||||
|
||||
## API Reference
|
||||
|
||||
<ApiReferenceIntro />
|
||||
|
@ -11,6 +11,8 @@ import WaspFileStructureNote from './\_wasp-file-structure-note.md';
|
||||
import GetUserFieldsType from './\_getuserfields-type.md';
|
||||
import ApiReferenceIntro from './\_api-reference-intro.md';
|
||||
import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md';
|
||||
import GoogleData from '../entities/_google-data.md';
|
||||
import AccessingUserDataNote from '../\_accessing-user-data-note.md';
|
||||
|
||||
Wasp supports Google Authentication out of the box.
|
||||
Google Auth is arguably the best external auth option, as most users on the web already have Google accounts.
|
||||
@ -495,6 +497,12 @@ export function getConfig() {
|
||||
|
||||
<UsingAuthNote />
|
||||
|
||||
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's Google ID like this:
|
||||
|
||||
<GoogleData />
|
||||
|
||||
<AccessingUserDataNote />
|
||||
|
||||
## API Reference
|
||||
|
||||
<ApiReferenceIntro />
|
||||
|
@ -11,6 +11,8 @@ import WaspFileStructureNote from './\_wasp-file-structure-note.md';
|
||||
import GetUserFieldsType from './\_getuserfields-type.md';
|
||||
import ApiReferenceIntro from './\_api-reference-intro.md';
|
||||
import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md';
|
||||
import KeycloakData from '../entities/_keycloak-data.md';
|
||||
import AccessingUserDataNote from '../\_accessing-user-data-note.md';
|
||||
|
||||
Wasp supports Keycloak Authentication out of the box.
|
||||
|
||||
@ -454,6 +456,12 @@ export function getConfig() {
|
||||
|
||||
<UsingAuthNote />
|
||||
|
||||
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's Keycloak ID like this:
|
||||
|
||||
<KeycloakData />
|
||||
|
||||
<AccessingUserDataNote />
|
||||
|
||||
## API Reference
|
||||
|
||||
<ApiReferenceIntro />
|
||||
|
@ -5,11 +5,12 @@ title: Username & Password
|
||||
import { Required } from '@site/src/components/Tag';
|
||||
import MultipleIdentitiesWarning from './\_multiple-identities-warning.md';
|
||||
import ReadMoreAboutAuthEntities from './\_read-more-about-auth-entities.md';
|
||||
import GetUsername from './entities/\_get-username.md';
|
||||
import UserSignupFieldsExplainer from './\_user-signup-fields-explainer.md';
|
||||
import UserFieldsExplainer from './\_user-fields.md';
|
||||
import UsernameData from './entities/\_username-data.md';
|
||||
import AccessingUserDataNote from './\_accessing-user-data-note.md';
|
||||
|
||||
Wasp supports username & password authentication out of the box with login and signup flows. It provides you with the server-side implementation and the UI components for the client-side.
|
||||
Wasp supports username & password authentication out of the box with login and signup flows. It provides you with the server-side implementation and the UI components for the client side.
|
||||
|
||||
## Setting Up Username & Password Authentication
|
||||
|
||||
@ -609,13 +610,11 @@ We suggest using the built-in field validators for your authentication flow. You
|
||||
|
||||
To read more about how to set up the logout button and how to get access to the logged-in user in our client and server code, read the [auth overview docs](../auth/overview).
|
||||
|
||||
### `getUsername`
|
||||
When you receive the `user` object [on the client or the server](./overview.md#accessing-the-logged-in-user), you'll be able to access the user's username like this:
|
||||
|
||||
If you are looking to access the user's username in your code, you can do that by accessing the info about the user that is stored in the `user.auth.identities` array.
|
||||
<UsernameData />
|
||||
|
||||
To make things a bit easier for you, Wasp offers the `getUsername` helper.
|
||||
|
||||
<GetUsername />
|
||||
<AccessingUserDataNote />
|
||||
|
||||
## API Reference
|
||||
|
||||
|
@ -160,7 +160,7 @@ Auth field customization is no longer possible using the `_waspCustomValidations
|
||||
|
||||
:::
|
||||
|
||||
You can read more about the new auth system in the [Auth Entities](./auth/entities) section.
|
||||
You can read more about the new auth system in the [Accessing User Data](./auth/entities) section.
|
||||
|
||||
## How to Migrate?
|
||||
|
||||
@ -642,7 +642,8 @@ You should see the new `Auth`, `AuthIdentity` and `Session` tables in your datab
|
||||
|
||||
Instead, you can now use `getUsername(user)` to get the username obtained from Username & Password auth method, or `getEmail(user)` to get the email obtained from Email auth method.
|
||||
|
||||
Read more about the helpers in the [Auth Entities - Accessing the Auth Fields](auth/entities#accessing-the-auth-fields) section.
|
||||
Read more about the helpers in the [Accessing User Data](auth/entities#accessing-the-auth-fields) section.
|
||||
|
||||
1. Finally, **check that your app now fully works as it worked before**. If all the above steps were done correctly, everything should be working now.
|
||||
|
||||
:::info Migrating a deployed app
|
||||
|
267
web/docs/migrate-from-0-13-to-0-14.md
Normal file
267
web/docs/migrate-from-0-13-to-0-14.md
Normal file
@ -0,0 +1,267 @@
|
||||
---
|
||||
title: Migration from 0.13.X to 0.14.X
|
||||
---
|
||||
|
||||
:::note Are you on 0.11.X or earlier?
|
||||
|
||||
This guide only covers the migration from **0.13.X to 0.14.X**. If you are migrating from 0.11.X or earlier, please read the [migration guide from 0.11.X to 0.12.X](./migrate-from-0-11-to-0-12.md) first.
|
||||
|
||||
:::
|
||||
|
||||
## What's new in 0.14.0?
|
||||
|
||||
### Better auth user API
|
||||
|
||||
We introduced a much simpler API for accessing user auth fields like `username`, `email` or `isEmailVerified` on the `user` object. You don't need to use helper functions every time you want to access the user's `username` or extra steps to get proper typing.
|
||||
|
||||
## How to migrate?
|
||||
|
||||
### Migrate how you access user auth fields
|
||||
|
||||
We had to make a couple of breaking changes to reach the new simpler API.
|
||||
|
||||
Follow the steps below to migrate:
|
||||
|
||||
1. **Replace the `getUsername` helper** with `user.identities.username.id`
|
||||
|
||||
If you didn't use the `getUsername` helper in your code, you can skip this step.
|
||||
|
||||
This helper changed and it no longer works with the `user` you receive as a prop on a page or through the `context`. You'll need to replace it with `user.identities.username.id`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="before" label="Before">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getUsername, AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const username = getUsername(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
import { getUsername } from 'wasp/auth'
|
||||
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const username = getUsername(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const username = user.identities.username?.id
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const username = context.user.identities.username?.id
|
||||
// ...
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
1. **Replace the `getEmail` helper** with `user.identities.email.id`
|
||||
|
||||
If you didn't use the `getEmail` helper in your code, you can skip this step.
|
||||
|
||||
This helper changed and it no longer works with the `user` you receive as a prop on a page or through the `context`. You'll need to replace it with `user.identities.email.id`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="before" label="Before">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getEmail, AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const email = getEmail(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
import { getEmail } from 'wasp/auth'
|
||||
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const email = getEmail(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const email = user.identities.email?.id
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const email = context.user.identities.email?.id
|
||||
// ...
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
1. **Replace accessing `providerData`** with `user.identities.<provider>.<value>`
|
||||
|
||||
If you didn't use any data from the `providerData` object, you can skip this step.
|
||||
|
||||
Replace `<provider>` with the provider name (for example `username`, `email`, `google`, `github`, etc.) and `<value>` with the field you want to access (for example `isEmailVerified`).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="before" label="Before">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { findUserIdentity, AuthUser } from 'wasp/auth'
|
||||
|
||||
function getProviderData(user: AuthUser) {
|
||||
const emailIdentity = findUserIdentity(user, 'email')
|
||||
// We needed this before check for proper type support
|
||||
return emailIdentity && 'isEmailVerified' in emailIdentity.providerData
|
||||
? emailIdentity.providerData
|
||||
: null
|
||||
}
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const providerData = getProviderData(user)
|
||||
const isEmailVerified = providerData ? providerData.isEmailVerified : null
|
||||
// ...
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
// The email object is properly typed, so we can access `isEmailVerified` directly
|
||||
const isEmailVerified = user.identities.email?.isEmailVerified
|
||||
// ...
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
1. **Use `getFirstProviderUserId` directly** on the user object
|
||||
|
||||
If you didn't use `getFirstProviderUserId` in your code, you can skip this step.
|
||||
|
||||
You should replace `getFirstProviderUserId(user)` with `user.getFirstProviderUserId()`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="before" label="Before">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { getFirstProviderUserId, AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const userId = getFirstProviderUserId(user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
import { getFirstProviderUserId } from 'wasp/auth'
|
||||
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const userId = getFirstProviderUserId(context.user)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const userId = user.getFirstProviderUserId()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const userId = user.getFirstProviderUserId()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
1. **Replace `findUserIdentity`** with checks on `user.identities.<provider>`
|
||||
|
||||
If you didn't use `findUserIdentity` in your code, you can skip this step.
|
||||
|
||||
Instead of using `findUserIdentity` to get the identity object, you can directly check if the identity exists on the `identities` object.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="before" label="Before">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { findUserIdentity, AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
const usernameIdentity = findUserIdentity(user, 'username')
|
||||
if (usernameIdentity) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
import { findUserIdentity } from 'wasp/auth'
|
||||
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
const usernameIdentity = findUserIdentity(context.user, 'username')
|
||||
if (usernameIdentity) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="after" label="After">
|
||||
|
||||
```tsx title="src/MainPage.tsx"
|
||||
import { AuthUser } from 'wasp/auth'
|
||||
|
||||
const MainPage = ({ user }: { user: AuthUser }) => {
|
||||
if (user.identities.username) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts title=src/tasks.ts
|
||||
export const createTask: CreateTask<...> = async (args, context) => {
|
||||
if (context.user.identities.username) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
That's it!
|
||||
|
||||
You should now be able to run your app with the new Wasp 0.14.0. We recommend reading through the updated [Accessing User Data](./auth/entities/entities.md) section to get a better understanding of the new API.
|
@ -140,6 +140,7 @@ module.exports = {
|
||||
'contact',
|
||||
'migrate-from-0-11-to-0-12',
|
||||
'migrate-from-0-12-to-0-13',
|
||||
'migrate-from-0-13-to-0-14'
|
||||
],
|
||||
},
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user