New auth model docs (#1620)

This commit is contained in:
Mihovil Ilakovac 2024-01-15 11:00:54 +01:00 committed by GitHub
parent c534a84b9c
commit ffe2509cb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1088 additions and 554 deletions

View File

@ -3,7 +3,7 @@ title: Custom HTTP API Endpoints
---
import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
In Wasp, the default client-server interaction mechanism is through [Operations](../data-model/operations/overview). However, if you need a specific URL method/path, or a specific response, Operations may not be suitable for you. For these cases, you can use an `api`. Best of all, they should look and feel very familiar.

View File

@ -2,7 +2,7 @@
title: Deploying with the Wasp CLI
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
Wasp CLI can deploy your full-stack application with only a single command.
The command automates the manual deployment process and is the recommended way of deploying Wasp apps.

View File

@ -5,7 +5,7 @@ title: Deploying Manually
import useBaseUrl from '@docusaurus/useBaseUrl';
import AddExternalAuthEnvVarsReminder from './\_addExternalAuthEnvVarsReminder.md'
import BuildingTheWebClient from './\_building-the-web-client.md'
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
We'll cover how to deploy your Wasp app manually to a variety of providers:

View File

@ -4,7 +4,7 @@ title: Sending Emails
import SendingEmailsInDevelopment from '../\_sendingEmailsInDevelopment.md'
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'
# Sending Emails

View File

@ -2,7 +2,7 @@
title: Recurring Jobs
---
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'
In most web apps, users send requests to the server and receive responses with some data. When the server responds quickly, the app feels responsive and smooth.

View File

@ -2,7 +2,7 @@
title: Type-Safe Links
---
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
If you are using Typescript, you can use Wasp's custom `Link` component to create type-safe links to other pages on your site.

View File

@ -3,7 +3,7 @@ title: Web Sockets
---
import useBaseUrl from '@docusaurus/useBaseUrl';
import { ShowForTs } from '@site/src/components/TsJsHelpers';
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
Wasp provides a fully integrated WebSocket experience by utilizing [Socket.IO](https://socket.io/) on the client and server.

View File

@ -0,0 +1,6 @@
:::caution Using multiple auth identities for a single user
Wasp currently doesn't support multiple auth identities for a single user. This means, for example, that a user can't have both an email-based auth identity and a Google-based auth identity. This is something we will add in the future with the introduction of the [account merging feature](https://github.com/wasp-lang/wasp/issues/954).
Account merging means that multiple auth identities can be merged into a single user account. For example, a user's email and Google identity can be merged into a single user account. Then the user can log in with either their email or Google account and they will be logged into the same account.
:::

View File

@ -0,0 +1 @@
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.

View File

@ -2,26 +2,23 @@
title: Email
---
import { Required } from '@site/src/components/Required';
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';
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.
![Auth UI](/img/authui/all_screens.gif)
:::caution Using email auth and social auth together
If a user signs up with Google or Github (and you set it up to save their social provider e-mail info on the `User` entity), they'll be able to reset their password and login with e-mail and password ✅
If a user signs up with the e-mail and password and then tries to login with a social provider (Google or Github), they won't be able to do that ❌
In the future, we will lift this limitation and enable smarter merging of accounts.
:::
<MultipleIdentitiesWarning />
## Setting Up Email Authentication
We'll need to take the following steps to set up email authentication:
1. Enable email authentication in the Wasp file
1. Add the user entity
1. Add the routes and pages
1. Add the `User` entity
1. Add the auth routes and pages
1. Use Auth UI components in our pages
1. Set up the email sender
@ -123,20 +120,16 @@ Read more about the `email` auth method options [here](#fields-in-the-email-dict
### 2. Add the User Entity
When email authentication is enabled, Wasp expects certain fields in your `userEntity`. Let's add these fields to our `main.wasp` file:
The `User` entity can be as simple as including only the `id` field:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp" {4-8}
```wasp title="main.wasp"
// 5. Define the user entity
entity User {=psl
// highlight-next-line
id Int @id @default(autoincrement())
email String? @unique
password String?
isEmailVerified Boolean @default(false)
emailVerificationSentAt DateTime?
passwordResetSentAt DateTime?
// Add your own fields below
// ...
psl=}
@ -144,15 +137,11 @@ psl=}
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp" {4-8}
```wasp title="main.wasp"
// 5. Define the user entity
entity User {=psl
// highlight-next-line
id Int @id @default(autoincrement())
email String? @unique
password String?
isEmailVerified Boolean @default(false)
emailVerificationSentAt DateTime?
passwordResetSentAt DateTime?
// Add your own fields below
// ...
psl=}
@ -160,7 +149,8 @@ psl=}
</TabItem>
</Tabs>
Read more about the `userEntity` fields [here](#userentity-fields).
<ReadMoreAboutAuthEntities />
### 3. Add the Routes and Pages
@ -469,7 +459,7 @@ Read more about setting up email senders in the [sending emails docs](../advance
That's it! We have set up email authentication in our app. 🎉
Running `wasp db migrate-dev` and then `wasp start` should give you a working app with email authentication. If you want to put some of the pages behind authentication, read the [using auth docs](../auth/overview).
Running `wasp db migrate-dev` and then `wasp start` should give you a working app with email authentication. If you want to put some of the pages behind authentication, read the [auth overview](../auth/overview).
## Login and Signup Flows
@ -500,7 +490,7 @@ Some of the behavior you get out of the box:
4. Password validation
Read more about the default password validation rules and how to override them in [using auth docs](../auth/overview).
Read more about the default password validation rules and how to override them in [auth overview docs](../auth/overview).
## Email Verification Flow
@ -595,9 +585,249 @@ Users can enter their new password there.
The content of the e-mail can be customized, read more about it [here](#passwordreset-passwordresetconfig-).
## Using The Auth
## Creating a Custom Sign-up Action
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 [using auth docs](../auth/overview).
:::caution Creating a custom sign-up action
We don't recommend creating a custom sign-up action unless you have a good reason to do so. It is a complex process and you can easily make a mistake that will compromise the security of your app.
:::
The code of your custom sign-up action can look like this:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp"
// ...
action customSignup {
fn: import { signup } from "@server/auth/signup.js",
}
```
```js title="src/server/auth/signup.js"
import {
ensurePasswordIsPresent,
ensureValidPassword,
ensureValidEmail,
} from '@wasp/auth/validation.js'
import {
createProviderId,
sanitizeAndSerializeProviderData,
deserializeAndSanitizeProviderData,
findAuthIdentity,
createUser,
} from '@wasp/auth/utils.js'
import {
createEmailVerificationLink,
sendEmailVerificationEmail,
} from '@wasp/auth/providers/email/utils.js'
export const signup = async (args, _context) => {
ensureValidEmail(args)
ensurePasswordIsPresent(args)
ensureValidPassword(args)
try {
const providerId = createProviderId('email', args.email)
const existingAuthIdentity = await findAuthIdentity(providerId)
if (existingAuthIdentity) {
const providerData = deserializeAndSanitizeProviderData(existingAuthIdentity.providerData)
// Your custom code here
} else {
// sanitizeAndSerializeProviderData will hash the user's password
const newUserProviderData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
isEmailVerified: false,
emailVerificationSentAt: null,
passwordResetSentAt: null,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)
// Verification link links to a client route e.g. /email-verification
const verificationLink = await createEmailVerificationLink(args.email, '/email-verification');
try {
await sendEmailVerificationEmail(
args.email,
{
from: {
name: "My App Postman",
email: "hello@itsme.com",
},
to: args.email,
subject: "Verify your email",
text: `Click the link below to verify your email: ${verificationLink}`,
html: `
<p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a>
`,
}
);
} catch (e: unknown) {
console.error("Failed to send email verification email:", e);
throw new HttpError(500, "Failed to send email verification email.");
}
}
} catch (e) {
return {
success: false,
message: e.message,
}
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: 'User created successfully',
}
}
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp"
// ...
action customSignup {
fn: import { signup } from "@server/auth/signup.js",
}
```
```ts title="src/server/auth/signup.ts"
import {
ensurePasswordIsPresent,
ensureValidPassword,
ensureValidEmail,
} from '@wasp/auth/validation.js'
import {
createProviderId,
sanitizeAndSerializeProviderData,
deserializeAndSanitizeProviderData,
findAuthIdentity,
createUser,
} from '@wasp/auth/utils.js'
import {
createEmailVerificationLink,
sendEmailVerificationEmail,
} from '@wasp/auth/providers/email/utils.js'
import type { CustomSignup } from '@wasp/actions/types'
type CustomSignupInput = {
email: string
password: string
}
type CustomSignupOutput = {
success: boolean
message: string
}
export const signup: CustomSignup<CustomSignupInput, CustomSignupOutput> = async (args, _context) => {
ensureValidEmail(args)
ensurePasswordIsPresent(args)
ensureValidPassword(args)
try {
const providerId = createProviderId('email', args.email)
const existingAuthIdentity = await findAuthIdentity(providerId)
if (existingAuthIdentity) {
const providerData = deserializeAndSanitizeProviderData<'email'>(existingAuthIdentity.providerData)
// Your custom code here
} else {
// sanitizeAndSerializeProviderData will hash the user's password
const newUserProviderData = await sanitizeAndSerializeProviderData<'email'>({
hashedPassword: args.password,
isEmailVerified: false,
emailVerificationSentAt: null,
passwordResetSentAt: null,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)
// Verification link links to a client route e.g. /email-verification
const verificationLink = await createEmailVerificationLink(args.email, '/email-verification');
try {
await sendEmailVerificationEmail(
args.email,
{
from: {
name: "My App Postman",
email: "hello@itsme.com",
},
to: args.email,
subject: "Verify your email",
text: `Click the link below to verify your email: ${verificationLink}`,
html: `
<p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a>
`,
}
);
} catch (e: unknown) {
console.error("Failed to send email verification email:", e);
throw new HttpError(500, "Failed to send email verification email.");
}
}
} catch (e) {
return {
success: false,
message: e.message,
}
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: 'User created successfully',
}
}
```
</TabItem>
</Tabs>
We suggest using the built-in field validators for your authentication flow. You can import them from `@wasp/auth/validation.js`. These are the same validators that Wasp uses internally for the default authentication flow.
#### Email
- `ensureValidEmail(args)`
Checks if the email is valid and throws an error if it's not. Read more about the validation rules [here](../auth/overview#default-validations).
#### Password
- `ensurePasswordIsPresent(args)`
Checks if the password is present and throws an error if it's not.
- `ensureValidPassword(args)`
Checks if the password is valid and throws an error if it's not. Read more about the validation rules [here](../auth/overview#default-validations).
## Using Auth
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`
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.
To make things a bit easier for you, Wasp offers the `getEmail` helper.
<GetEmail />
## API Reference
@ -608,7 +838,7 @@ Let's go over the options we can specify when using email authentication.
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp" {18-25}
```wasp title="main.wasp" {18-21}
app myApp {
title: "My app",
// ...
@ -625,20 +855,14 @@ app myApp {
// ...
}
// Using email auth requires the `userEntity` to have at least the following fields
entity User {=psl
id Int @id @default(autoincrement())
email String? @unique
password String?
isEmailVerified Boolean @default(false)
emailVerificationSentAt DateTime?
passwordResetSentAt DateTime?
psl=}
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp" {18-25}
```wasp title="main.wasp" {18-21}
app myApp {
title: "My app",
// ...
@ -655,27 +879,13 @@ app myApp {
// ...
}
// Using email auth requires the `userEntity` to have at least the following fields
entity User {=psl
id Int @id @default(autoincrement())
email String? @unique
password String?
isEmailVerified Boolean @default(false)
emailVerificationSentAt DateTime?
passwordResetSentAt DateTime?
psl=}
```
</TabItem>
</Tabs>
Email auth requires that `userEntity` specified in `auth` contains:
- optional `email` field of type `String`
- optional `password` field of type `String`
- `isEmailVerified` field of type `Boolean` with a default value of `false`
- optional `emailVerificationSentAt` field of type `DateTime`
- optional `passwordResetSentAt` field of type `DateTime`
### Fields in the `email` dict
<Tabs groupId="js-ts">

View File

@ -0,0 +1,48 @@
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/client/MainPage.jsx"
import { getEmail } from '@wasp/auth/user'
const MainPage = ({ user }) => {
const email = getEmail(user)
// ...
}
```
```js title=src/server/tasks.js
import { getEmail } from '@wasp/auth/user.js'
export const createTask = async (args, context) => {
const email = getEmail(context.user)
// ...
}
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```tsx title="src/client/MainPage.tsx"
import { getEmail } from '@wasp/auth/user'
import { User as AuthenticatedUser } from '@wasp/auth/types'
const MainPage = ({ user }: { user: AuthenticatedUser }) => {
const email = getEmail(user)
// ...
}
```
```ts title=src/server/tasks.ts
import { getEmail } from '@wasp/auth/user.js'
export const createTask: CreateTask<...> = async (args, context) => {
const email = getEmail(context.user)
// ...
}
```
</TabItem>
</Tabs>

View File

@ -0,0 +1,48 @@
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/client/MainPage.jsx"
import { getUsername } from '@wasp/auth/user'
const MainPage = ({ user }) => {
const username = getUsername(user)
// ...
}
```
```js title=src/server/tasks.js
import { getUsername } from '@wasp/auth/user.js'
export const createTask = async (args, context) => {
const username = getUsername(context.user)
// ...
}
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```tsx title="src/client/MainPage.tsx"
import { getUsername } from '@wasp/auth/user'
import { User as AuthenticatedUser } from '@wasp/auth/types'
const MainPage = ({ user }: { user: AuthenticatedUser }) => {
const username = getUsername(user)
// ...
}
```
```ts title=src/server/tasks.ts
import { getUsername } from '@wasp/auth/user.js'
export const createTask: CreateTask<...> = async (args, context) => {
const username = getUsername(context.user)
// ...
}
```
</TabItem>
</Tabs>

View File

@ -0,0 +1,424 @@
---
title: Auth Entities
---
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';
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.
## Entities Explained
To store user information, Wasp creates a few entities behind the scenes. In this section, we will explain what entities are created and how they are connected.
### 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.
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.
```wasp
entity User {=psl
id Int @id @default(autoincrement())
// Any other fields you want to store about the user
psl=}
```
You **own** the user entity and you can modify it as you wish. You can add new fields to it, remove fields from it, or change the type of the fields. You can also add new relations to it or remove existing relations from it.
<ImgWithCaption alt="Auth Entities in a Wasp App" source="img/auth-entities/model.png" caption="Auth Entities in a Wasp App"/>
On the other hand, the `Auth` and `AuthIdentity` entities are created behind the scenes and are used to store the user's login credentials. You as the developer don't need to care about this entity most of the time. Wasp **owns** these entities.
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.
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.
- Each `Auth` entity can have multiple `AuthIdentity` entities.
- In this example, the `Auth` entity has two `AuthIdentity` entities: one for the email-based auth and one for the Google-based auth.
<MultipleIdentitiesWarning />
### `Auth` Entity <Internal />
Wasp's internal `Auth` entity is used to connect the business logic user, `User` with the user's login credentials.
```wasp
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)
identities AuthIdentity[]
psl=}
```
The `Auth` fields:
- `id` is a unique identifier of the `Auth` entity.
- `userId` is a foreign key to the `User` entity.
- It is used to connect the `Auth` entity with the business logic user.
- `user` is a relation to the `User` entity.
- This relation is injected on the `User` entity as well.
- `identities` is a relation to the `AuthIdentity` entity.
### `AuthIdentity` Entity <Internal />
The `AuthIdentity` entity is used to store the user's login credentials for various authentication methods.
```wasp
entity AuthIdentity {=psl
providerName String
providerUserId String
providerData String @default("{}")
authId String
auth Auth @relation(fields: [authId], references: [id], onDelete: Cascade)
@@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.
- For example, the user's email or Google ID.
- `providerData` is a JSON string that contains additional data about the user from the authentication provider.
- For example, for password based auth, this field contains the user's hashed password.
- This field is a `String` and not a `Json` type because [Prisma doesn't support the `Json` type for SQLite](https://github.com/prisma/prisma/issues/3786).
- `authId` is a foreign key to the `Auth` entity.
- It is used to connect the `AuthIdentity` 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/client/MainPage.jsx"
import { getFirstProviderUserId } from '@wasp/auth/user'
const MainPage = ({ user }) => {
const userId = getFirstProviderUserId(user)
// ...
}
```
```js title=src/server/tasks.js
import { getFirstProviderUserId } from '@wasp/auth/user.js'
export const createTask = async (args, context) => {
const userId = getFirstProviderUserId(context.user)
// ...
}
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```tsx title="src/client/MainPage.tsx"
import { getFirstProviderUserId } from '@wasp/auth/user'
import { User as AuthenticatedUser } from '@wasp/auth/types'
const MainPage = ({ user }: { user: AuthenticatedUser }) => {
const userId = getFirstProviderUserId(user)
// ...
}
```
```ts title=src/server/tasks.ts
import { getFirstProviderUserId } from '@wasp/auth/user.js'
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/client/MainPage.jsx"
import { findUserIdentity } from '@wasp/auth/user'
const MainPage = ({ user }) => {
const emailIdentity = findUserIdentity(user, 'email')
const googleIdentity = findUserIdentity(user, 'google')
if (emailIdentity) {
// ...
} else if (googleIdentity) {
// ...
}
// ...
}
```
```js title=src/server/tasks.js
import { findUserIdentity } from '@wasp/auth/user.js'
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/client/MainPage.tsx"
import { findUserIdentity } from '@wasp/auth/user'
import { User as AuthenticatedUser } from '@wasp/auth/types'
const MainPage = ({ user }: { user: AuthenticatedUser }) => {
const emailIdentity = findUserIdentity(user, 'email')
const googleIdentity = findUserIdentity(user, 'google')
if (emailIdentity) {
// ...
} else if (googleIdentity) {
// ...
}
// ...
}
```
```ts title=src/server/tasks.ts
import { findUserIdentity } from '@wasp/auth/user.js'
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.
:::info Custom Signup Examples
In the [Email](./email#creating-a-custom-sign-up-action) section of the docs we give you an example for custom email signup and in the [Username & password](./username-and-pass#2-creating-your-custom-sign-up-action) section of the docs we give you an example for custom username & password signup.
:::
Below is a simplified version of a custom signup action which you probably wouldn't use in your app but it shows you how you can use the `Auth` and `AuthIdentity` entities to create a custom signup action.
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp"
// ...
action customSignup {
fn: import { signup } from "@server/auth/signup.js",
entities: [User]
}
```
```js title="src/server/auth/signup.js"
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from '@wasp/auth/utils.js'
export const signup = async (args, { entities: { User } }) => {
try {
// Provider ID is a combination of the provider name and the provider user ID
// And it is used to uniquely identify the user in your app
const providerId = createProviderId('username', args.username)
// sanitizeAndSerializeProviderData hashes the password and returns a JSON string
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)
// This is equivalent to:
// await User.create({
// data: {
// auth: {
// create: {
// identities: {
// create: {
// providerName: 'username',
// providerUserId: args.username
// providerData,
// },
// },
// }
// },
// }
// })
} catch (e) {
return {
success: false,
message: e.message,
}
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: 'User created successfully',
}
}
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp"
// ...
action customSignup {
fn: import { signup } from "@server/auth/signup.js",
entities: [User]
}
```
```ts title="src/server/auth/signup.ts"
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from '@wasp/auth/utils.js'
import type { CustomSignup } from '@wasp/actions/types'
type CustomSignupInput = {
username: string
password: string
}
type CustomSignupOutput = {
success: boolean
message: string
}
export const signup: CustomSignup<
CustomSignupInput,
CustomSignupOutput
> = async (args, { entities: { User } }) => {
try {
// Provider ID is a combination of the provider name and the provider user ID
// And it is used to uniquely identify the user in your app
const providerId = createProviderId('username', args.username)
// sanitizeAndSerializeProviderData hashes the password and returns a JSON string
const providerData = await sanitizeAndSerializeProviderData<'username'>({
hashedPassword: args.password,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)
// This is equivalent to:
// await User.create({
// data: {
// auth: {
// create: {
// identities: {
// create: {
// providerName: 'username',
// providerUserId: args.username
// providerData,
// },
// },
// }
// },
// }
// })
} catch (e) {
return {
success: false,
message: e.message,
}
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: 'User created successfully',
}
}
```
</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.

View File

@ -1,11 +1,12 @@
---
title: Using Auth
title: Overview
---
import { AuthMethodsGrid } from "@site/src/components/AuthMethodsGrid";
import { Required } from "@site/src/components/Required";
import { Required } from '@site/src/components/Tag';
import ReadMoreAboutAuthEntities from './\_read-more-about-auth-entities.md';
Auth is an essential piece of any serious application. Coincidentally, Wasp provides authentication and authorization support out of the box.
Auth is an essential piece of any serious application. That's why Wasp provides authentication and authorization support out of the box.
Here's a 1-minute tour of how full-stack auth works in Wasp:
@ -13,7 +14,7 @@ Here's a 1-minute tour of how full-stack auth works in Wasp:
<iframe src="https://www.youtube.com/embed/Qiro77q-ulI?si=y8Rejsbjb1HJC6FA" frameborder="1" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
Enabling auth for your app is optional and can be done by configuring the `auth` field of the `app` declaration.
Enabling auth for your app is optional and can be done by configuring the `auth` field of your `app` declaration.
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
@ -24,7 +25,6 @@ app MyApp {
//...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
usernameAndPassword: {}, // use this or email, not both
email: {}, // use this or usernameAndPassword, not both
@ -47,7 +47,6 @@ app MyApp {
//...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
usernameAndPassword: {}, // use this or email, not both
email: {}, // use this or usernameAndPassword, not both
@ -152,7 +151,33 @@ const LogoutButton = () => {
## Accessing the logged-in user
You can get access to the `user` object both in the backend and on the frontend.
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:
```js
const user = {
id: "19c7d164-b5cb-4dde-a0cc-0daea77cf854",
// 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: { ... },
},
]
},
}
```
<ReadMoreAboutAuthEntities />
### On the client
@ -206,11 +231,11 @@ page AccountPage {
```
```tsx title="client/pages/Account.tsx"
import type { User } from '@wasp/entities'
import { User as AuthenticatedUser } from '@wasp/auth/types'
import Button from './Button'
import logout from '@wasp/auth/logout'
const AccountPage = ({ user }: { user: User }) => {
const AccountPage = ({ user }: { user: AuthenticatedUser }) => {
return (
<div>
<Button onClick={logout}>Logout</Button>
@ -301,7 +326,7 @@ Since the `user` prop is only available in a page's React component: use the `us
#### Using the `context.user` object
When authentication is enabled, all [queries and actions](../data-model/operations/overview) have access to the `user` object through the `context` argument. `context.user` contains all User entity's fields, except for the password.
When authentication is enabled, all [queries and actions](../data-model/operations/overview) have access to the `user` object through the `context` argument. `context.user` contains all User entity's fields and the auth identities connected to the user. We strip out the `hashedPassword` field from the identities for security reasons.
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
@ -363,23 +388,43 @@ To implement access control in your app, each operation must check `context.user
When using WebSockets, the `user` object is also available on the `socket.data` object. Read more in the [WebSockets section](../advanced/web-sockets#websocketfn-function).
## User entity
## User Entity
### Password hashing
### Password Hashing
You don't need to worry about hashing the password yourself. Even when directly using the Prisma client and calling `create()` with a plain-text password, Wasp's middleware makes sure to hash the password before storing it in the database.
For example, if you need to update a user's password, you can safely use the Prisma client to do so, e.g., inside an Action:
If you are saving a user's password in the database, you should **never** save it as plain text. You can use Wasp's helper functions for serializing and deserializing provider data which will automatically hash the password for you:
```wasp title="main.wasp"
// ...
action updatePassword {
fn: import { updatePassword } from "@server/auth.js",
}
```
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```js title="src/server/actions.js"
import {
createProviderId,
findAuthIdentity,
updateAuthIdentityProviderData,
deserializeAndSanitizeProviderData,
} from '@wasp/auth/utils.js';
export const updatePassword = async (args, context) => {
return context.entities.User.update({
where: { id: args.userId },
data: {
password: 'New pwd which will be hashed automatically!',
},
const providerId = createProviderId('email', args.email)
const authIdentity = await findAuthIdentity(providerId)
if (!authIdentity) {
throw new HttpError(400, "Unknown user")
}
const providerData = deserializeAndSanitizeProviderData(authIdentity.providerData)
// Updates the password and hashes it automatically.
await updateAuthIdentityProviderData(providerId, providerData, {
hashedPassword: args.password,
})
}
```
@ -388,22 +433,29 @@ export const updatePassword = async (args, context) => {
<TabItem value="ts" label="TypeScript">
```ts title="src/server/actions.ts"
import {
createProviderId,
findAuthIdentity,
updateAuthIdentityProviderData,
deserializeAndSanitizeProviderData,
} from '@wasp/auth/utils.js';
import type { UpdatePassword } from '@wasp/actions/types'
import type { User } from '@wasp/entities'
type UpdatePasswordPayload = {
userId: User['id']
}
export const updatePassword: UpdatePassword<
UpdatePasswordPayload,
User
{ email: string; password: string },
void,
> = async (args, context) => {
return context.entities.User.update({
where: { id: args.userId },
data: {
password: 'New pwd which will be hashed automatically!',
},
const providerId = createProviderId('email', args.email)
const authIdentity = await findAuthIdentity(providerId)
if (!authIdentity) {
throw new HttpError(400, "Unknown user")
}
const providerData = deserializeAndSanitizeProviderData<'email'>(authIdentity.providerData)
// Updates the password and hashes it automatically.
await updateAuthIdentityProviderData(providerId, providerData, {
hashedPassword: args.password,
})
}
```
@ -411,26 +463,26 @@ export const updatePassword: UpdatePassword<
</TabItem>
</Tabs>
### Default validations
### Default Validations
When you are using the default authentication flow, Wasp validates the fields with some default validations. These validations run if you use Wasp's built-in [Auth UI](/docs/auth/ui) or if you use the provided auth actions.
When you are using the default authentication flow, Wasp validates the fields with some default validations. These validations run if you use Wasp's built-in [Auth UI](./ui) or if you use the provided auth actions.
If you decide to create your [custom auth actions](/docs/auth/username-and-pass#2-creating-your-custom-sign-up-action), you'll need to run the validations yourself.
If you decide to create your [custom auth actions](./username-and-pass#2-creating-your-custom-sign-up-action), you'll need to run the validations yourself.
Default validations depend on the auth method you use.
#### Username & password
#### Username & Password
If you use [Username & password](../auth/username-and-pass) authentication, the default validations are:
If you use [Username & password](./username-and-pass) authentication, the default validations are:
- The `username` must not be empty
- The `password` must not be empty, have at least 8 characters, and contain a number
Note that `username`s are stored in a **case-sensitive** manner.
Note that `username`s are stored in a **case-insensitive** manner.
#### Email
If you use [Email](../auth/email) authentication, the default validations are:
If you use [Email](./email) authentication, the default validations are:
- The `email` must not be empty and a valid email address
- The `password` must not be empty, have at least 8 characters, and contain a number
@ -521,8 +573,6 @@ app crudTesting {
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
address String?
psl=}
```
@ -566,8 +616,6 @@ app crudTesting {
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
address String?
psl=}
```
@ -876,7 +924,6 @@ Read more about the render function in the [API Reference](#signupform-customiza
//...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
usernameAndPassword: {}, // use this or email, not both
email: {}, // use this or usernameAndPassword, not both
@ -900,7 +947,6 @@ app MyApp {
//...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
usernameAndPassword: {}, // use this or email, not both
email: {}, // use this or usernameAndPassword, not both
@ -922,74 +968,9 @@ app MyApp {
#### `userEntity: entity` <Required />
The entity representing the user. Its mandatory fields depend on your chosen auth method.
The entity representing the user connected to your business logic.
#### `externalAuthEntity: entity`
Wasp requires you to set the field `auth.externalAuthEntity` for all authentication methods relying on an external authorizatino provider (e.g., Google). You also need to tweak the Entity referenced by `auth.userEntity`, as shown below.
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp {4,14} title="main.wasp"
//...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
//...
entity User {=psl
id Int @id @default(autoincrement())
//...
externalAuthAssociations SocialLogin[]
psl=}
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp {4,14} title="main.wasp"
//...
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
//...
entity User {=psl
id Int @id @default(autoincrement())
//...
externalAuthAssociations SocialLogin[]
psl=}
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
</TabItem>
</Tabs>
:::note
The same `externalAuthEntity` can be used across different social login providers (e.g., both GitHub and Google can use the same entity).
:::
See [Google docs](../auth/social-auth/google) and [GitHub docs](../auth/social-auth/github) for more details.
<ReadMoreAboutAuthEntities />
#### `methods: dict` <Required />

View File

@ -1,10 +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.
Also, if the `userEntity` has:
- A `username` field: Wasp sets it to a random username (e.g. `nice-blue-horse-14357`).
- A `password` field: Wasp sets it to a random string.
This is a historical coupling between `auth` methods we plan to remove in the future.
<!-- This snippet is used in overview.md, google.md and github.md -->

View File

@ -1,4 +1,4 @@
Wasp lets you override the default behavior. You can create custom setups, such as allowing users to define a custom username rather instead of getting a randomly generated one.
By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider.
There are two mechanisms (functions) used for overriding the default behavior:

View File

@ -8,7 +8,6 @@ app myApp {
// Defining entities
entity User { ... }
entity SocialLogin { ... }
// Defining routes and pages
route LoginRoute { ... }

View File

@ -24,7 +24,7 @@ Let's walk through enabling Github Authentication, explain some of the default s
Enabling GitHub Authentication comes down to a series of steps:
1. Enabling GitHub authentication in the Wasp file.
1. Adding the necessary Entities.
1. Adding the `User` entity.
1. Creating a GitHub OAuth app.
1. Adding the neccessary Routes and Pages
1. Using Auth UI components in our Pages.
@ -49,13 +49,9 @@ app myApp {
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
// highlight-next-line
// 2. Specify the SocialLogin entity (we'll define it next)
// highlight-next-line
externalAuthEntity: SocialLogin,
methods: {
// highlight-next-line
// 3. Enable Github Auth
// 2. Enable Github Auth
// highlight-next-line
gitHub: {}
},
@ -78,13 +74,9 @@ app myApp {
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
// highlight-next-line
// 2. Specify the SocialLogin entity (we'll define it next)
// highlight-next-line
externalAuthEntity: SocialLogin,
methods: {
// highlight-next-line
// 3. Enable Github Auth
// 2. Enable Github Auth
// highlight-next-line
gitHub: {}
},
@ -96,35 +88,20 @@ app myApp {
</TabItem>
</Tabs>
### 2. Add the Entities
### 2. Add the User Entity
Let's now define the entities acting as `app.auth.userEntity` and `app.auth.externalAuthEntity`:
Let's now define the `app.auth.userEntity` entity:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp"
// ...
// highlight-next-line
// 4. Define the User entity
// 3. Define the User entity
// highlight-next-line
entity User {=psl
id Int @id @default(autoincrement())
// ...
externalAuthAssociations SocialLogin[]
psl=}
// highlight-next-line
// 5. Define the SocialLogin entity
// highlight-next-line
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
@ -133,34 +110,17 @@ psl=}
```wasp title="main.wasp"
// ...
// highlight-next-line
// 4. Define the User entity
// 3. Define the User entity
// highlight-next-line
entity User {=psl
id Int @id @default(autoincrement())
// ...
externalAuthAssociations SocialLogin[]
psl=}
// highlight-next-line
// 5. Define the SocialLogin entity
// highlight-next-line
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
</TabItem>
</Tabs>
`externalAuthEntity` and `userEntity` are explained in [the social auth overview](../../auth/social-auth/overview#social-login-entity).
### 3. Creating a GitHub OAuth App
To use GitHub as an authentication method, you'll first need to create a GitHub OAuth App and provide Wasp with your client key and secret. Here's how you do it:
@ -313,7 +273,7 @@ Add `gitHub: {}` to the `auth.methods` dictionary to use it with default setting
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title=main.wasp {10}
```wasp title=main.wasp
app myApp {
wasp: {
version: "^0.11.0"
@ -321,8 +281,8 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
// highlight-next-line
gitHub: {}
},
onAuthFailedRedirectTo: "/login"
@ -333,7 +293,7 @@ app myApp {
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title=main.wasp {10}
```wasp title=main.wasp
app myApp {
wasp: {
version: "^0.11.0"
@ -341,8 +301,8 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
// highlight-next-line
gitHub: {}
},
onAuthFailedRedirectTo: "/login"
@ -366,7 +326,7 @@ app myApp {
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp" {11-12,22}
```wasp title="main.wasp"
app myApp {
wasp: {
version: "^0.11.0"
@ -374,10 +334,11 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
gitHub: {
// highlight-next-line
configFn: import { getConfig } from "@server/auth/github.js",
// highlight-next-line
getUserFieldsFn: import { getUserFields } from "@server/auth/github.js"
}
},
@ -389,7 +350,6 @@ entity User {=psl
id Int @id @default(autoincrement())
username String @unique
displayName String
externalAuthAssociations SocialLogin[]
psl=}
// ...
@ -416,7 +376,7 @@ export function getConfig() {
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp" {11-12,22}
```wasp title="main.wasp"
app myApp {
wasp: {
version: "^0.11.0"
@ -424,10 +384,11 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
gitHub: {
// highlight-next-line
configFn: import { getConfig } from "@server/auth/github.js",
// highlight-next-line
getUserFieldsFn: import { getUserFields } from "@server/auth/github.js"
}
},
@ -439,7 +400,6 @@ entity User {=psl
id Int @id @default(autoincrement())
username String @unique
displayName String
externalAuthAssociations SocialLogin[]
psl=}
// ...
@ -480,7 +440,7 @@ export function getConfig() {
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp" {11-12}
```wasp title="main.wasp"
app myApp {
wasp: {
version: "^0.11.0"
@ -488,10 +448,11 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
gitHub: {
// highlight-next-line
configFn: import { getConfig } from "@server/auth/github.js",
// highlight-next-line
getUserFieldsFn: import { getUserFields } from "@server/auth/github.js"
}
},
@ -503,7 +464,7 @@ app myApp {
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp" {11-12}
```wasp title="main.wasp"
app myApp {
wasp: {
version: "^0.11.0"
@ -511,10 +472,11 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
gitHub: {
// highlight-next-line
configFn: import { getConfig } from "@server/auth/github.js",
// highlight-next-line
getUserFieldsFn: import { getUserFields } from "@server/auth/github.js"
}
},

View File

@ -24,7 +24,7 @@ Let's walk through enabling Google authentication, explain some of the default s
Enabling Google Authentication comes down to a series of steps:
1. Enabling Google authentication in the Wasp file.
1. Adding the necessary Entities.
1. Adding the `User` entity.
1. Creating a Google OAuth app.
1. Adding the neccessary Routes and Pages
1. Using Auth UI components in our Pages.
@ -45,17 +45,11 @@ app myApp {
},
title: "My App",
auth: {
// highlight-next-line
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
// highlight-next-line
// 2. Specify the SocialLogin entity (we'll define it next)
// highlight-next-line
externalAuthEntity: SocialLogin,
methods: {
// highlight-next-line
// 3. Enable Google Auth
// 2. Enable Google Auth
// highlight-next-line
google: {}
},
@ -74,17 +68,11 @@ app myApp {
},
title: "My App",
auth: {
// highlight-next-line
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
// highlight-next-line
// 2. Specify the SocialLogin entity (we'll define it next)
// highlight-next-line
externalAuthEntity: SocialLogin,
methods: {
// highlight-next-line
// 3. Enable Google Auth
// 2. Enable Google Auth
// highlight-next-line
google: {}
},
@ -96,37 +84,22 @@ app myApp {
</TabItem>
</Tabs>
`externalAuthEntity` and `userEntity` are explained in [the social auth overview](../../auth/social-auth/overview#social-login-entity).
`userEntity` is explained in [the social auth overview](../../auth/social-auth/overview#social-login-entity).
### 2. Adding the Entities
### 2. Adding the User Entity
Let's now define the entities acting as `app.auth.userEntity` and `app.auth.externalAuthEntity`:
Let's now define the `app.auth.userEntity` entity:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp"
// ...
// highlight-next-line
// 4. Define the User entity
// 3. Define the User entity
// highlight-next-line
entity User {=psl
id Int @id @default(autoincrement())
// ...
externalAuthAssociations SocialLogin[]
psl=}
// highlight-next-line
// 5. Define the SocialLogin entity
// highlight-next-line
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
@ -135,26 +108,11 @@ psl=}
```wasp title="main.wasp"
// ...
// highlight-next-line
// 4. Define the User entity
// 3. Define the User entity
// highlight-next-line
entity User {=psl
id Int @id @default(autoincrement())
// ...
externalAuthAssociations SocialLogin[]
psl=}
// highlight-next-line
// 5. Define the SocialLogin entity
// highlight-next-line
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
@ -356,7 +314,7 @@ Add `google: {}` to the `auth.methods` dictionary to use it with default setting
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title=main.wasp {10}
```wasp title=main.wasp
app myApp {
wasp: {
version: "^0.11.0"
@ -364,8 +322,8 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
// highlight-next-line
google: {}
},
onAuthFailedRedirectTo: "/login"
@ -376,7 +334,7 @@ app myApp {
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title=main.wasp {10}
```wasp title=main.wasp
app myApp {
wasp: {
version: "^0.11.0"
@ -384,8 +342,8 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
// highlight-next-line
google: {}
},
onAuthFailedRedirectTo: "/login"
@ -409,7 +367,7 @@ app myApp {
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp" {11-12,22}
```wasp title="main.wasp"
app myApp {
wasp: {
version: "^0.11.0"
@ -417,10 +375,11 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {
// highlight-next-line
configFn: import { getConfig } from "@server/auth/google.js",
// highlight-next-line
getUserFieldsFn: import { getUserFields } from "@server/auth/google.js"
}
},
@ -432,7 +391,6 @@ entity User {=psl
id Int @id @default(autoincrement())
username String @unique
displayName String
externalAuthAssociations SocialLogin[]
psl=}
// ...
@ -459,7 +417,7 @@ export function getConfig() {
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp" {11-12,22}
```wasp title="main.wasp"
app myApp {
wasp: {
version: "^0.11.0"
@ -467,10 +425,11 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {
// highlight-next-line
configFn: import { getConfig } from "@server/auth/google.js",
// highlight-next-line
getUserFieldsFn: import { getUserFields } from "@server/auth/google.js"
}
},
@ -482,7 +441,6 @@ entity User {=psl
id Int @id @default(autoincrement())
username String @unique
displayName String
externalAuthAssociations SocialLogin[]
psl=}
// ...
@ -523,7 +481,7 @@ export function getConfig() {
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp" {11-12}
```wasp title="main.wasp"
app myApp {
wasp: {
version: "^0.11.0"
@ -531,10 +489,11 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {
// highlight-next-line
configFn: import { getConfig } from "@server/auth/google.js",
// highlight-next-line
getUserFieldsFn: import { getUserFields } from "@server/auth/google.js"
}
},
@ -546,7 +505,7 @@ app myApp {
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp" {11-12}
```wasp title="main.wasp"
app myApp {
wasp: {
version: "^0.11.0"
@ -554,10 +513,11 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {
// highlight-next-line
configFn: import { getConfig } from "@server/auth/google.js",
// highlight-next-line
getUserFieldsFn: import { getUserFields } from "@server/auth/google.js"
}
},

View File

@ -23,15 +23,12 @@ Wasp currently supports the following social login providers:
<SocialAuthGrid />
## Social Login Entity
## User Entity
Wasp requires you to declare a `userEntity` for all `auth` methods (social or otherwise).
This field tells Wasp which Entity represents the user.
Additionally, when using `auth` methods that rely on external providers(e.g., _Google_), you must also declare an `externalAuthEntity`.
This tells Wasp which Entity represents the user's link with the social provider.
Both fields fall under `app.auth`. Here's what the full setup looks like:
Here's what the full setup looks like:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
@ -45,8 +42,6 @@ app myApp {
auth: {
// highlight-next-line
userEntity: User,
// highlight-next-line
externalAuthEntity: SocialLogin,
methods: {
google: {}
},
@ -58,18 +53,6 @@ app myApp {
entity User {=psl
id Int @id @default(autoincrement())
//...
externalAuthAssociations SocialLogin[]
psl=}
// highlight-next-line
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
@ -85,8 +68,6 @@ app myApp {
auth: {
// highlight-next-line
userEntity: User,
// highlight-next-line
externalAuthEntity: SocialLogin,
methods: {
google: {}
},
@ -98,18 +79,6 @@ app myApp {
entity User {=psl
id Int @id @default(autoincrement())
//...
externalAuthAssociations SocialLogin[]
psl=}
// highlight-next-line
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
```
@ -122,23 +91,23 @@ To learn more about what the fields on these entities represent, look at the [AP
</small>
:::note
Wasp uses the same `externalAuthEntity` for all social login providers (e.g. both GitHub and Google use the same entity).
:::
## Default Behavior
<DefaultBehaviour />
## Overrides
Wasp lets you override the default behavior. You can create custom setups, such as allowing users to define a custom username rather instead of getting a randomly generated one.
By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider.
If you wish to store more information about the user, you can override the default behavior. You can do this by defining the `getUserFieldsFn` and `configFn` functions in `main.wasp` for each provider.
You can create custom signup setups, such as allowing users to define a custom username after they sign up with a social provider.
### Allowing User to Set Their Username
If you want to modify the signup flow (e.g., let users choose their own usernames), you will need to go through three steps:
1. The first step is adding a `isSignupComplete` property to your `User` Entity. This field will signals whether the user has completed the signup process.
1. The first step is adding a `isSignupComplete` property to your `User` Entity. This field will signal whether the user has completed the signup process.
2. The second step is overriding the default signup behavior.
3. The third step is implementing the rest of your signup flow and redirecting users where appropriate.
@ -155,7 +124,6 @@ entity User {=psl
username String? @unique
// highlight-next-line
isSignupComplete Boolean @default(false)
externalAuthAssociations SocialLogin[]
psl=}
```
@ -168,7 +136,6 @@ entity User {=psl
username String? @unique
// highlight-next-line
isSignupComplete Boolean @default(false)
externalAuthAssociations SocialLogin[]
psl=}
```
@ -190,7 +157,6 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {
// highlight-next-line
@ -225,7 +191,6 @@ app myApp {
title: "My App",
auth: {
userEntity: User,
externalAuthEntity: SocialLogin,
methods: {
google: {
// highlight-next-line
@ -395,103 +360,4 @@ For more information on:
Check the provider-specific API References:
<SocialAuthGrid pagePart="#api-reference" />
### The `externalAuthEntity` and Its Fields
Using social login providers requires you to define _an External Auth Entity_ and declare it with the `app.auth.externalAuthEntity` field.
This Entity holds the data relevant to the social provider.
All social providers share the same Entity.
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title=main.wasp {4-10}
// ...
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
// ...
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title=main.wasp {4-10}
// ...
entity SocialLogin {=psl
id Int @id @default(autoincrement())
provider String
providerId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
createdAt DateTime @default(now())
@@unique([provider, providerId, userId])
psl=}
// ...
```
</TabItem>
</Tabs>
:::info
You don't need to know these details, you can just copy and paste the entity definition above and you are good to go.
:::
The Entity acting as `app.auth.externalAuthEntity` must include the following fields:
- `provider` - The provider's name (e.g. `google`, `github`, etc.).
- `providerId` - The user's ID on the provider's platform.
- `userId` - The user's ID on your platform (this references the `id` field from the Entity acting as `app.auth.userEntity`).
- `user` - A relation to the `userEntity` (see [the `userEntity` section](#expected-fields-on-the-userentity)) for more details.
- `createdAt` - A timestamp of when the association was created.
- `@@unique([provider, providerId, userId])` - A unique constraint on the combination of `provider`, `providerId` and `userId`.
### Expected Fields on the `userEntity`
Using Social login providers requires you to add one extra field to the Entity acting as `app.auth.userEntity`:
- `externalAuthAssociations` - A relation to the `externalAuthEntity` (see [the `externalAuthEntity` section](#the-externalauthentity-and-its-fields) for more details).
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title=main.wasp {6}
// ...
entity User {=psl
id Int @id @default(autoincrement())
//...
externalAuthAssociations SocialLogin[]
psl=}
// ...
```
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title=main.wasp {6}
// ...
entity User {=psl
id Int @id @default(autoincrement())
//...
externalAuthAssociations SocialLogin[]
psl=}
// ...
```
</TabItem>
</Tabs>
<SocialAuthGrid pagePart="#api-reference" />

View File

@ -218,7 +218,7 @@ export function SignupPage() {
It will automatically show the correct authentication providers based on your `main.wasp` file.
Read more about customizing the signup process like adding additional fields or extra UI in the [Using Auth](../auth/overview#customizing-the-signup-process) section.
Read more about customizing the signup process like adding additional fields or extra UI in the [Auth Overview](../auth/overview#customizing-the-signup-process) section.
### Forgot Password Form

View File

@ -2,7 +2,10 @@
title: Username & Password
---
import { Required } from '@site/src/components/Required';
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';
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.
@ -10,8 +13,8 @@ Wasp supports username & password authentication out of the box with login and s
To set up username authentication we need to:
1. Enable username authentication in the Wasp file
1. Add the user entity
1. Add the routes and pages
1. Add the `User` entity
1. Add the auth routes and pages
1. Use Auth UI components in our pages
Structure of the `main.wasp` file we will end up with:
@ -80,17 +83,16 @@ Read more about the `usernameAndPassword` auth method options [here](#fields-in-
### 2. Add the User Entity
When username authentication is enabled, Wasp expects certain fields in your `userEntity`. Let's add these fields to our `main.wasp` file:
The `User` entity can be as simple as including only the `id` field:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
```wasp title="main.wasp" {4-5}
```wasp title="main.wasp"
// 3. Define the user entity
entity User {=psl
// highlight-next-line
id Int @id @default(autoincrement())
username String @unique
password String
// Add your own fields below
// ...
psl=}
@ -98,12 +100,11 @@ psl=}
</TabItem>
<TabItem value="ts" label="TypeScript">
```wasp title="main.wasp" {4-5}
```wasp title="main.wasp"
// 3. Define the user entity
entity User {=psl
// highlight-next-line
id Int @id @default(autoincrement())
username String @unique
password String
// Add your own fields below
// ...
psl=}
@ -111,7 +112,7 @@ psl=}
</TabItem>
</Tabs>
Read more about the `userEntity` fields [here](#userentity-fields).
<ReadMoreAboutAuthEntities />
### 3. Add the Routes and Pages
@ -261,17 +262,19 @@ We imported the generated Auth UI components and used them in our pages. Read mo
That's it! We have set up username authentication in our app. 🎉
Running `wasp db migrate-dev` and then `wasp start` should give you a working app with username authentication. If you want to put some of the pages behind authentication, read the [using auth docs](../auth/overview).
Running `wasp db migrate-dev` and then `wasp start` should give you a working app with username authentication. If you want to put some of the pages behind authentication, read the [auth overview docs](../auth/overview).
<MultipleIdentitiesWarning />
## Customizing the Auth Flow
The login and signup flows are pretty standard: they allow the user to sign up and then log in with their username and password. The signup flow validates the username and password and then creates a new user entity in the database.
Read more about the default username and password validation rules in the [using auth docs](../auth/overview#default-validations).
Read more about the default username and password validation rules in the [auth overview docs](../auth/overview#default-validations).
If you require more control in your authentication flow, you can achieve that in the following ways:
1. Create your UI and use `signup` and `login` actions.
1. Create your custom sign-up action which use the Prisma client, along with your custom code.
1. Create your custom sign-up action which uses the lower-level API, along with your custom code.
### 1. Using the `signup` and `login` actions
@ -375,7 +378,7 @@ It takes one argument:
- `password: string` <Required />
:::info
By default, Wasp will only save the `username` and `password` fields. If you want to add extra fields to your signup process, read about [defining extra signup fields](/docs/auth/overview#customizing-the-signup-process).
By default, Wasp will only save the `username` and `password` fields. If you want to add extra fields to your signup process, read about [defining extra signup fields](../auth/overview#customizing-the-signup-process).
:::
You can use it like this:
@ -471,7 +474,6 @@ The code of your custom sign-up action can look like this:
action customSignup {
fn: import { signup } from "@server/auth/signup.js",
entities: [User]
}
```
@ -482,19 +484,29 @@ import {
ensureValidPassword,
ensureValidUsername,
} from '@wasp/auth/validation.js'
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from '@wasp/auth/utils.js'
export const signup = async (args, { entities: { User } }) => {
export const signup = async (args, _context) => {
ensureValidUsername(args)
ensurePasswordIsPresent(args)
ensureValidPassword(args)
try {
await User.create({
data: {
username: args.username,
password: args.password, // Password is hashed automatically by Wasp
},
const providerId = createProviderId('username', args.username)
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)
} catch (e) {
return {
success: false,
@ -519,7 +531,6 @@ export const signup = async (args, { entities: { User } }) => {
action customSignup {
fn: import { signup } from "@server/auth/signup.js",
entities: [User]
}
```
@ -529,6 +540,11 @@ import {
ensureValidPassword,
ensureValidUsername,
} from '@wasp/auth/validation.js'
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from '@wasp/auth/utils.js'
import type { CustomSignup } from '@wasp/actions/types'
type CustomSignupInput = {
@ -543,19 +559,24 @@ type CustomSignupOutput = {
export const signup: CustomSignup<
CustomSignupInput,
CustomSignupOutput
> = async (args, { entities: { User } }) => {
> = async (args, _context) => {
ensureValidUsername(args)
ensurePasswordIsPresent(args)
ensureValidPassword(args)
try {
await User.create({
data: {
username: args.username,
password: args.password, // Password is hashed automatically by Wasp
},
const providerId = createProviderId('username', args.username)
const providerData = await sanitizeAndSerializeProviderData<'username'>({
hashedPassword: args.password,
})
} catch (e: any) {
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
)
} catch (e) {
return {
success: false,
message: e.message,
@ -580,7 +601,7 @@ We suggest using the built-in field validators for your authentication flow. You
- `ensureValidUsername(args)`
Checks if the username is valid and throws an error if it's not. Read more about the validation rules [here](/docs/auth/overview#default-validations).
Checks if the username is valid and throws an error if it's not. Read more about the validation rules [here](../auth/overview#default-validations).
#### Password
@ -590,11 +611,19 @@ We suggest using the built-in field validators for your authentication flow. You
- `ensureValidPassword(args)`
Checks if the password is valid and throws an error if it's not. Read more about the validation rules [here](/docs/auth/overview#default-validations).
Checks if the password is valid and throws an error if it's not. Read more about the validation rules [here](../auth/overview#default-validations).
## Using Auth
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 [using auth docs](../auth/overview).
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`
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.
To make things a bit easier for you, Wasp offers the `getUsername` helper.
<GetUsername />
## API Reference
@ -618,11 +647,8 @@ app myApp {
}
}
// Wasp requires the `userEntity` to have at least the following fields
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
psl=}
```
</TabItem>
@ -643,21 +669,13 @@ app myApp {
}
}
// Wasp requires the `userEntity` to have at least the following fields
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
psl=}
```
</TabItem>
</Tabs>
Username & password auth requires that `userEntity` specified in `auth` contains:
- `username` field of type `String`
- `password` field of type `String`
### Fields in the `usernameAndPassword` dict
<Tabs groupId="js-ts">
@ -705,4 +723,4 @@ app myApp {
`usernameAndPassword` dict doesn't have any options at the moment.
:::
You can read about the rest of the `auth` options in the [using auth](../auth/overview) section of the docs.
You can read about the rest of the `auth` options in the [auth overview](../auth/overview) section of the docs.

View File

@ -2,7 +2,7 @@
title: Databases
---
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
[Entities](../data-model/entities.md), [Operations](../data-model/operations/overview) and [Automatic CRUD](../data-model/crud.md) together make a high-level interface for working with your app's data. Still, all that data has to live somewhere, so let's see how Wasp deals with databases.

View File

@ -2,7 +2,7 @@
title: Automatic CRUD
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
import { ShowForTs } from '@site/src/components/TsJsHelpers';
import ImgWithCaption from '@site/blog/components/ImgWithCaption'
@ -89,8 +89,6 @@ app tasksCrudApp {
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
tasks Task[]
psl=}

View File

@ -2,7 +2,7 @@
title: Actions
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
import { ShowForTs } from '@site/src/components/TsJsHelpers';
import SuperjsonNote from './\_superjson-note.md';

View File

@ -2,7 +2,7 @@
title: Overview
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
While Entities enable help you define your app's data model and relationships, Operations are all about working with this data.

View File

@ -2,7 +2,7 @@
title: Queries
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
import { ShowForTs } from '@site/src/components/TsJsHelpers';
import SuperjsonNote from './\_superjson-note.md';

View File

@ -72,8 +72,6 @@ Let's then add the data models for your recipes. We will want to have Users and
entity User {=psl // Data models are defined using Prisma Schema Language.
id Int @id @default(autoincrement())
username String @unique
password String
recipes Recipe[]
psl=}

View File

@ -2,7 +2,7 @@
title: Customizing the App
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
Each Wasp project can have only one `app` type declaration. It is used to configure your app and its components.

View File

@ -19,24 +19,16 @@ First, we'll create a Todo list for what needs to be done (luckily we have an ap
## Creating a User Entity
Since Wasp manages authentication, it expects certain fields to exist on the `User` entity. Specifically, it expects a unique `username` field and a `password` field, both of which should be strings.
Since Wasp manages authentication, it will create [the auth related entities](../auth/entities) for us in the background, that we don't have to worry about. However, we still need to add the `User` entity that will help us keep track of which user owns which tasks.
```wasp title="main.wasp"
// ...
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
psl=}
```
As we talked about earlier, we have to remember to update the database schema:
```sh
wasp db migrate-dev
```
## Adding Auth to the Project
Next, we want to tell Wasp that we want to use full-stack [authentication](../auth/overview) in our app:
@ -63,6 +55,12 @@ app TodoApp {
// ...
```
As we talked about earlier, we have to remember to update the database schema:
```sh
wasp db migrate-dev
```
By doing this, Wasp will create:
- [Auth UI](../auth/ui) with login and signup forms.
@ -250,9 +248,9 @@ const MainPage = ({ user }) => {
<TabItem value="ts" label="TypeScript">
```tsx {3} title="src/client/MainPage.tsx"
import { User } from '@wasp/entities'
import { User as AuthenticatedUser } from '@wasp/auth/types'
const MainPage = ({ user }: { user: User }) => {
const MainPage = ({ user }: { user: AuthenticatedUser }) => {
// Do something with the user
}
```
@ -271,25 +269,31 @@ wasp db studio
```
<img alt="Database demonstration - password hashing"
src={useBaseUrl('img/wasp_db_hash_demonstration.gif')}
src={useBaseUrl('img/wasp_user_in_db.gif')}
style={{ border: "1px solid black" }}
/>
We see there is a user and that its password is already hashed 🤯
You'll notice that we now have a `User` entity in the database alongside the `Task` entity.
However, you will notice that if you try logging in as different users and creating some tasks, all users share the same tasks. That's because we haven't yet updated the queries and actions to have per-user tasks. Let's do that next.
<small>
You might notice some extra Prisma models like `Auth` and `AuthIdentity` that Wasp created for us. You don't need to care about these right now, but if you are curious, you can read more about them [here](../auth/entities).
</small>
## Defining a User-Task Relation
First, let's define a one-to-many relation between users and tasks (check the [Prisma docs on relations](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations)):
```wasp {7,14-15} title="main.wasp"
```wasp title="main.wasp"
// ...
entity User {=psl
id Int @id @default(autoincrement())
username String @unique
password String
// highlight-next-line
tasks Task[]
psl=}
@ -297,7 +301,9 @@ entity Task {=psl
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
// highlight-next-line
user User? @relation(fields: [userId], references: [id])
// highlight-next-line
userId Int?
psl=}

View File

@ -67,6 +67,7 @@ module.exports = {
'auth/social-auth/google',
],
},
'auth/entities/entities',
],
},
{

View File

@ -1,22 +0,0 @@
import * as React from 'react'
const color = '#f59e0b'
export function Required() {
return (
<span
style={{
border: `2px solid ${color}`,
display: 'inline-block',
padding: '0.2em 0.4em',
color: color,
borderRadius: '0.4em',
fontSize: '0.8em',
lineHeight: '1',
fontWeight: 'bold',
}}
>
required
</span>
)
}

View File

@ -0,0 +1,37 @@
import * as React from 'react'
export const Tag = ({
color,
children,
}: React.PropsWithChildren<{
color: string
}>) => {
return (
<span
style={{
border: `2px solid ${color}`,
display: 'inline-block',
padding: '0.2em 0.4em',
color: color,
borderRadius: '0.4em',
fontSize: '0.8em',
lineHeight: '1',
fontWeight: 'bold',
}}
>
{children}
</span>
)
}
// Used to mark something as internal to
// Wasp and not to be used by the user.
export function Internal() {
return <Tag color="#0b62f5">internal</Tag>
}
// Used to mark something as required e.g. required
// fields in Wasp file.
export function Required() {
return <Tag color="#f59e0b">required</Tag>
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

View File

@ -3,7 +3,7 @@ title: Custom HTTP API Endpoints
---
import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
In Wasp, the default client-server interaction mechanism is through [Operations](../data-model/operations/overview). However, if you need a specific URL method/path, or a specific response, Operations may not be suitable for you. For these cases, you can use an `api`. Best of all, they should look and feel very familiar.

View File

@ -2,7 +2,7 @@
title: Deploying with the Wasp CLI
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
Wasp CLI can deploy your full-stack application with only a single command.
The command automates the manual deployment process and is the recommended way of deploying Wasp apps.

View File

@ -4,7 +4,7 @@ title: Sending Emails
import SendingEmailsInDevelopment from '../\_sendingEmailsInDevelopment.md'
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'
# Sending Emails

View File

@ -2,7 +2,7 @@
title: Recurring Jobs
---
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'
In most web apps, users send requests to the server and receive responses with some data. When the server responds quickly, the app feels responsive and smooth.

View File

@ -2,7 +2,7 @@
title: Type-Safe Links
---
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
If you are using Typescript, you can use Wasp's custom `Link` component to create type-safe links to other pages on your site.

View File

@ -3,7 +3,7 @@ title: Web Sockets
---
import useBaseUrl from '@docusaurus/useBaseUrl';
import { ShowForTs } from '@site/src/components/TsJsHelpers';
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
Wasp provides a fully integrated WebSocket experience by utilizing [Socket.IO](https://socket.io/) on the client and server.

View File

@ -2,7 +2,7 @@
title: Email
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
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.

View File

@ -3,7 +3,7 @@ title: Using Auth
---
import { AuthMethodsGrid } from "@site/src/components/AuthMethodsGrid";
import { Required } from "@site/src/components/Required";
import { Required } from "@site/src/components/Tag";
Auth is an essential piece of any serious application. Coincidentally, Wasp provides authentication and authorization support out of the box.

View File

@ -2,7 +2,7 @@
title: Username & Password
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
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.

View File

@ -2,7 +2,7 @@
title: Databases
---
import { Required } from '@site/src/components/Required'
import { Required } from '@site/src/components/Tag'
[Entities](../data-model/entities.md), [Operations](../data-model/operations/overview) and [Automatic CRUD](../data-model/crud.md) together make a high-level interface for working with your app's data. Still, all that data has to live somewhere, so let's see how Wasp deals with databases.

View File

@ -2,7 +2,7 @@
title: Automatic CRUD
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
import { ShowForTs } from '@site/src/components/TsJsHelpers';
import ImgWithCaption from '@site/blog/components/ImgWithCaption'

View File

@ -2,7 +2,7 @@
title: Actions
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
import { ShowForTs } from '@site/src/components/TsJsHelpers';
import SuperjsonNote from './\_superjson-note.md';

View File

@ -2,7 +2,7 @@
title: Overview
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
While Entities enable help you define your app's data model and relationships, Operations are all about working with this data.

View File

@ -2,7 +2,7 @@
title: Queries
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
import { ShowForTs } from '@site/src/components/TsJsHelpers';
import SuperjsonNote from './\_superjson-note.md';

View File

@ -2,7 +2,7 @@
title: Customizing the App
---
import { Required } from '@site/src/components/Required';
import { Required } from '@site/src/components/Tag';
Each Wasp project can have only one `app` type declaration. It is used to configure your app and its components.