Bumps Wasp to 0.15.0 (#2321)

This commit is contained in:
Mihovil Ilakovac 2024-10-04 13:53:34 +02:00 committed by GitHub
parent 451251b52c
commit 05dfcc860e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 274 additions and 224 deletions

View File

@ -11,7 +11,7 @@
<a href="https://discord.gg/rzdnErX"><img alt="discord" src="https://img.shields.io/discord/686873244791210014?label=chat%20@%20discord"/></a> <a href="https://discord.gg/rzdnErX"><img alt="discord" src="https://img.shields.io/discord/686873244791210014?label=chat%20@%20discord"/></a>
</p> </p>
------ ---
<p align="center"> <p align="center">
<a href="https://wasp-lang.dev">Web page</a> | <a href="https://wasp-lang.dev/docs">Docs</a> <a href="https://wasp-lang.dev">Web page</a> | <a href="https://wasp-lang.dev/docs">Docs</a>
@ -23,22 +23,25 @@ Wasp (**W**eb **A**pplication **Sp**ecification) is a Rails-like framework for R
Build your app in a day and deploy it with a single CLI command! Build your app in a day and deploy it with a single CLI command!
### Why is Wasp awesome ### Why is Wasp awesome
- 🚀 **Quick start**: Due to its expressiveness, you can create and deploy a production-ready web app from scratch with very few lines of concise, consistent, declarative code. - 🚀 **Quick start**: Due to its expressiveness, you can create and deploy a production-ready web app from scratch with very few lines of concise, consistent, declarative code.
- 😌 **No boilerplate**: By abstracting away complex full-stack features, there is less boilerplate code. That means less code to maintain and understand! It also means easier upgrades. - 😌 **No boilerplate**: By abstracting away complex full-stack features, there is less boilerplate code. That means less code to maintain and understand! It also means easier upgrades.
- 🔓 **No lock-in**: You can deploy the Wasp app anywhere you like. There is no lock-in into specific providers; you have full control over the code (and can actually check it out in .wasp/ dir if you are interested ). - 🔓 **No lock-in**: You can deploy the Wasp app anywhere you like. There is no lock-in into specific providers; you have full control over the code (and can actually check it out in .wasp/ dir if you are interested ).
### Features ### Features
🔒 Full-stack Auth, 🖇️ RPC (Client <-> Server), 🚀 Simple Deployment, ⚙ Jobs, ✉️ Email Sending, 🛟 Full-stack Type Safety, ...
🔒 Full-stack Auth, 🖇️ RPC (Client <-> Server), 🚀 Simple Deployment, ⚙ Jobs, ✉️ Email Sending, 🛟 Full-stack Type Safety, ...
### Code example ### Code example
Simple Wasp config file in which you describe the high-level details of your web app: Simple Wasp config file in which you describe the high-level details of your web app:
```js ```js
// file: main.wasp // file: main.wasp
app todoApp { app todoApp {
title: "ToDo App", // visible in the browser tab title: "ToDo App", // visible in the browser tab
wasp: { version: "^0.14.0" }, wasp: { version: "^0.15.0" },
auth: { // full-stack auth out-of-the-box auth: { // full-stack auth out-of-the-box
userEntity: User, methods: { email: {...} } userEntity: User, methods: { email: {...} }
} }
@ -81,9 +84,11 @@ For more information about Wasp, check [**docs**](https://wasp-lang.dev/docs).
# Get started # Get started
Run Run
``` ```
curl -sSL https://get.wasp-lang.dev/installer.sh | sh curl -sSL https://get.wasp-lang.dev/installer.sh | sh
``` ```
to install Wasp on OSX/Linux/WSL(Win). From there, just follow the instructions to run your first app in less than a minute! to install Wasp on OSX/Linux/WSL(Win). From there, just follow the instructions to run your first app in less than a minute!
For more details, check out [the docs](https://wasp-lang.dev/docs). For more details, check out [the docs](https://wasp-lang.dev/docs).
@ -114,9 +119,10 @@ The core of Wasp is built in Haskell, but there are also a lot of non-Haskell pa
Even if you don't plan to submit any code, just joining the discussion on discord [![Discord](https://img.shields.io/discord/686873244791210014?label=chat%20on%20discord)](https://discord.gg/rzdnErX) and giving your feedback is already great and helps a lot (motivates us and helps us figure out how to shape Wasp)! Even if you don't plan to submit any code, just joining the discussion on discord [![Discord](https://img.shields.io/discord/686873244791210014?label=chat%20on%20discord)](https://discord.gg/rzdnErX) and giving your feedback is already great and helps a lot (motivates us and helps us figure out how to shape Wasp)!
You can also: You can also:
- :star: Star this repo to show your interest/support.
- :mailbox: Stay updated by subscribing to our [email list](https://wasp-lang.dev#signup). - :star: Star this repo to show your interest/support.
- :speech_balloon: Join the discussion at https://github.com/wasp-lang/wasp/discussions . - :mailbox: Stay updated by subscribing to our [email list](https://wasp-lang.dev#signup).
- :speech_balloon: Join the discussion at https://github.com/wasp-lang/wasp/discussions .
# Careers # Careers
@ -143,5 +149,3 @@ Check our [careers](https://wasp-lang.notion.site/Wasp-Careers-59fd1682c80d446f9
<a href="https://github.com/MarianoMiguel"><img src="https://github.com/MarianoMiguel.png" width="50px" alt="MarianoMiguel" /></a> <a href="https://github.com/MarianoMiguel"><img src="https://github.com/MarianoMiguel.png" width="50px" alt="MarianoMiguel" /></a>
<a href="https://github.com/Tech4Money"><img src="https://github.com/Tech4Money.png" width="50px" alt="Tech4Money" /></a> <a href="https://github.com/Tech4Money"><img src="https://github.com/Tech4Money.png" width="50px" alt="Tech4Money" /></a>
<a href="https://github.com/haseeb-heaven"><img src="https://github.com/haseeb-heaven.png" width="50px" alt="haseeb-heaven" /></a> <a href="https://github.com/haseeb-heaven"><img src="https://github.com/haseeb-heaven.png" width="50px" alt="haseeb-heaven" /></a>

View File

@ -1,6 +1,6 @@
app hackathonBetaSubmissions { app hackathonBetaSubmissions {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "Hackathon Submissions" title: "Hackathon Submissions"
} }

View File

@ -1,6 +1,6 @@
app streaming { app streaming {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "streaming" title: "streaming"
} }

View File

@ -1,6 +1,6 @@
app Thoughts { app Thoughts {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "Thoughts", title: "Thoughts",
db: { db: {

View File

@ -1,6 +1,6 @@
app TodoTypescript { app TodoTypescript {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "ToDo TypeScript", title: "ToDo TypeScript",

View File

@ -1,6 +1,6 @@
app TodoApp { app TodoApp {
wasp: { wasp: {
version: "^0.14.0" // Pins the version of Wasp to use. version: "^0.15.0" // Pins the version of Wasp to use.
}, },
title: "TodoApp", // Used as the browser tab title. Note that all strings in Wasp are double quoted! title: "TodoApp", // Used as the browser tab title. Note that all strings in Wasp are double quoted!
auth: { auth: {

View File

@ -1,6 +1,6 @@
app TodoApp { app TodoApp {
wasp: { wasp: {
version: "^0.14.0" // Pins the version of Wasp to use. version: "^0.15.0" // Pins the version of Wasp to use.
}, },
title: "TodoApp", // Used as the browser tab title. Note that all strings in Wasp are double quoted! title: "TodoApp", // Used as the browser tab title. Note that all strings in Wasp are double quoted!
auth: { auth: {

View File

@ -1,6 +1,6 @@
app waspello { app waspello {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "Waspello", title: "Waspello",

View File

@ -1,6 +1,6 @@
app waspleau { app waspleau {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "Waspleau", title: "Waspleau",

View File

@ -1,6 +1,6 @@
app whereDoWeEat { app whereDoWeEat {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "where-do-we-eat", title: "where-do-we-eat",
client: { client: {

View File

@ -226,7 +226,7 @@ waspGhOrgName = "wasp-lang"
-- By tagging templates for each version of Wasp CLI, we ensure that each release of -- By tagging templates for each version of Wasp CLI, we ensure that each release of
-- Wasp CLI uses correct version of templates, that work with it. -- Wasp CLI uses correct version of templates, that work with it.
waspVersionTemplateGitTag :: String waspVersionTemplateGitTag :: String
waspVersionTemplateGitTag = "wasp-v0.14-template" waspVersionTemplateGitTag = "wasp-v0.15-template"
findTemplateByString :: [StarterTemplate] -> String -> Maybe StarterTemplate findTemplateByString :: [StarterTemplate] -> String -> Maybe StarterTemplate
findTemplateByString templates query = find ((== query) . show) templates findTemplateByString templates query = find ((== query) . show) templates

View File

@ -1,6 +1,6 @@
app waspBuild { app waspBuild {
wasp: { wasp: {
version: "^0.14.2" version: "^0.15.0"
}, },
title: "waspBuild" title: "waspBuild"
} }

View File

@ -1,6 +1,6 @@
app waspCompile { app waspCompile {
wasp: { wasp: {
version: "^0.14.2" version: "^0.15.0"
}, },
title: "waspCompile" title: "waspCompile"
} }

View File

@ -1,6 +1,6 @@
app waspComplexTest { app waspComplexTest {
wasp: { wasp: {
version: "^0.14.2" version: "^0.15.0"
}, },
auth: { auth: {
userEntity: User, userEntity: User,

View File

@ -1,6 +1,6 @@
app waspJob { app waspJob {
wasp: { wasp: {
version: "^0.14.2" version: "^0.15.0"
}, },
title: "waspJob" title: "waspJob"
} }

View File

@ -1,6 +1,6 @@
app waspMigrate { app waspMigrate {
wasp: { wasp: {
version: "^0.14.2" version: "^0.15.0"
}, },
title: "waspMigrate" title: "waspMigrate"
} }

View File

@ -1,6 +1,6 @@
app waspNew { app waspNew {
wasp: { wasp: {
version: "^0.14.2" version: "^0.15.0"
}, },
title: "waspNew" title: "waspNew"
} }

View File

@ -1,6 +1,6 @@
app crudTesting { app crudTesting {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
head: [ head: [
"<link rel=\"stylesheet\" href=\"https://unpkg.com/mvp.css@1.12/mvp.css\">" "<link rel=\"stylesheet\" href=\"https://unpkg.com/mvp.css@1.12/mvp.css\">"

View File

@ -1,6 +1,6 @@
app pgVectorExample { app pgVectorExample {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "PG Vector Example", title: "PG Vector Example",
client: { client: {

View File

@ -1,6 +1,6 @@
app TodoTypescript { app TodoTypescript {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "ToDo TypeScript", title: "ToDo TypeScript",

View File

@ -1,6 +1,6 @@
app todoApp { app todoApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "ToDo App", title: "ToDo App",
// head: [], // head: [],

View File

@ -1,6 +1,6 @@
app todoApp { app todoApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "ToDo App", title: "ToDo App",
auth: { auth: {

View File

@ -6,7 +6,7 @@ cabal-version: 2.4
-- Consider using hpack, or maybe even hpack-dhall. -- Consider using hpack, or maybe even hpack-dhall.
name: waspc name: waspc
version: 0.14.2 version: 0.15.0
description: Please see the README on GitHub at <https://github.com/wasp-lang/wasp/waspc#readme> description: Please see the README on GitHub at <https://github.com/wasp-lang/wasp/waspc#readme>
homepage: https://github.com/wasp-lang/wasp/waspc#readme homepage: https://github.com/wasp-lang/wasp/waspc#readme
bug-reports: https://github.com/wasp-lang/wasp/issues bug-reports: https://github.com/wasp-lang/wasp/issues

View File

@ -55,7 +55,7 @@ To use auth hooks, you must first declare them in the Wasp file:
```wasp ```wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
auth: { auth: {
userEntity: User, userEntity: User,
@ -77,7 +77,7 @@ app myApp {
```wasp ```wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
auth: { auth: {
userEntity: User, userEntity: User,
@ -583,7 +583,7 @@ If you want to refresh the token periodically, use a [Wasp Job](../advanced/jobs
```wasp ```wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
auth: { auth: {
userEntity: User, userEntity: User,
@ -605,7 +605,7 @@ app myApp {
```wasp ```wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
auth: { auth: {
userEntity: User, userEntity: User,

View File

@ -19,6 +19,7 @@ Wasp supports e-mail authentication out of the box, along with email verificatio
## Setting Up Email Authentication ## Setting Up Email Authentication
We'll need to take the following steps to set 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. Enable email authentication in the Wasp file
1. Add the `User` entity 1. Add the `User` entity
1. Add the auth routes and pages 1. Add the auth routes and pages
@ -49,7 +50,7 @@ Let's start with adding the following to our `main.wasp` file:
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -77,13 +78,14 @@ app myApp {
}, },
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -111,6 +113,7 @@ app myApp {
}, },
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -132,6 +135,7 @@ model User {
// ... // ...
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -144,12 +148,12 @@ model User {
// ... // ...
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
<ReadMoreAboutAuthEntities /> <ReadMoreAboutAuthEntities />
### 3. Add the Routes and Pages ### 3. Add the Routes and Pages
Next, we need to define the routes and pages for the authentication pages. Next, we need to define the routes and pages for the authentication pages.
@ -187,6 +191,7 @@ page EmailVerificationPage {
component: import { EmailVerification } from "@src/pages/auth.jsx", component: import { EmailVerification } from "@src/pages/auth.jsx",
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -218,6 +223,7 @@ page EmailVerificationPage {
component: import { EmailVerification } from "@src/pages/auth.tsx", component: import { EmailVerification } from "@src/pages/auth.tsx",
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -254,11 +260,10 @@ export function Login() {
</span> </span>
<br /> <br />
<span className="text-sm font-medium text-gray-900"> <span className="text-sm font-medium text-gray-900">
Forgot your password? <Link to="/request-password-reset">reset it</Link> Forgot your password? <Link to="/request-password-reset">reset it</Link>.
.
</span> </span>
</Layout> </Layout>
); )
} }
export function Signup() { export function Signup() {
@ -270,7 +275,7 @@ export function Signup() {
I already have an account (<Link to="/login">go to login</Link>). I already have an account (<Link to="/login">go to login</Link>).
</span> </span>
</Layout> </Layout>
); )
} }
export function EmailVerification() { export function EmailVerification() {
@ -282,7 +287,7 @@ export function EmailVerification() {
If everything is okay, <Link to="/login">go to login</Link> If everything is okay, <Link to="/login">go to login</Link>
</span> </span>
</Layout> </Layout>
); )
} }
export function RequestPasswordReset() { export function RequestPasswordReset() {
@ -290,7 +295,7 @@ export function RequestPasswordReset() {
<Layout> <Layout>
<ForgotPasswordForm /> <ForgotPasswordForm />
</Layout> </Layout>
); )
} }
export function PasswordReset() { export function PasswordReset() {
@ -302,22 +307,23 @@ export function PasswordReset() {
If everything is okay, <Link to="/login">go to login</Link> If everything is okay, <Link to="/login">go to login</Link>
</span> </span>
</Layout> </Layout>
); )
} }
// A layout component to center the content // A layout component to center the content
export function Layout({ children }) { export function Layout({ children }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
</div> </div>
); )
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -341,11 +347,10 @@ export function Login() {
</span> </span>
<br /> <br />
<span className="text-sm font-medium text-gray-900"> <span className="text-sm font-medium text-gray-900">
Forgot your password? <Link to="/request-password-reset">reset it</Link> Forgot your password? <Link to="/request-password-reset">reset it</Link>.
.
</span> </span>
</Layout> </Layout>
); )
} }
export function Signup() { export function Signup() {
@ -357,7 +362,7 @@ export function Signup() {
I already have an account (<Link to="/login">go to login</Link>). I already have an account (<Link to="/login">go to login</Link>).
</span> </span>
</Layout> </Layout>
); )
} }
export function EmailVerification() { export function EmailVerification() {
@ -369,7 +374,7 @@ export function EmailVerification() {
If everything is okay, <Link to="/login">go to login</Link> If everything is okay, <Link to="/login">go to login</Link>
</span> </span>
</Layout> </Layout>
); )
} }
export function RequestPasswordReset() { export function RequestPasswordReset() {
@ -377,7 +382,7 @@ export function RequestPasswordReset() {
<Layout> <Layout>
<ForgotPasswordForm /> <ForgotPasswordForm />
</Layout> </Layout>
); )
} }
export function PasswordReset() { export function PasswordReset() {
@ -389,22 +394,23 @@ export function PasswordReset() {
If everything is okay, <Link to="/login">go to login</Link> If everything is okay, <Link to="/login">go to login</Link>
</span> </span>
</Layout> </Layout>
); )
} }
// A layout component to center the content // A layout component to center the content
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
</div> </div>
); )
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -430,6 +436,7 @@ app myApp {
} }
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -442,6 +449,7 @@ app myApp {
} }
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -462,21 +470,22 @@ Running `wasp db migrate-dev` and then `wasp start` should give you a working ap
![Auth UI](/img/authui/signup.png) ![Auth UI](/img/authui/signup.png)
Some of the behavior you get out of the box: Some of the behavior you get out of the box:
1. Rate limiting 1. Rate limiting
We are limiting the rate of sign-up requests to **1 request per minute** per email address. This is done to prevent spamming. We are limiting the rate of sign-up requests to **1 request per minute** per email address. This is done to prevent spamming.
2. Preventing user email leaks 2. Preventing user email leaks
If somebody tries to signup with an email that already exists and it's verified, we _pretend_ that the account was created instead of saying it's an existing account. This is done to prevent leaking the user's email address. If somebody tries to signup with an email that already exists and it's verified, we _pretend_ that the account was created instead of saying it's an existing account. This is done to prevent leaking the user's email address.
3. Allowing registration for unverified emails 3. Allowing registration for unverified emails
If a user tries to register with an existing but **unverified** email, we'll allow them to do that. This is done to prevent bad actors from locking out other users from registering with their email address. If a user tries to register with an existing but **unverified** email, we'll allow them to do that. This is done to prevent bad actors from locking out other users from registering with their email address.
4. Password validation 4. Password validation
Read more about the default password validation rules and how to override them in [auth overview 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 ## Email Verification Flow
@ -505,6 +514,7 @@ emailVerification: {
clientRoute: EmailVerificationRoute, clientRoute: EmailVerificationRoute,
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -515,10 +525,11 @@ emailVerification: {
clientRoute: EmailVerificationRoute, clientRoute: EmailVerificationRoute,
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
When the user receives an e-mail, they receive a link that goes to the client route specified in the `clientRoute` field. In our case, this is the `EmailVerificationRoute` route we defined in the `main.wasp` file. When the user receives an e-mail, they receive a link that goes to the client route specified in the `clientRoute` field. In our case, this is the `EmailVerificationRoute` route we defined in the `main.wasp` file.
The content of the e-mail can be customized, read more about it [here](#emailverification-emailverificationconfig-). The content of the e-mail can be customized, read more about it [here](#emailverification-emailverificationconfig-).
@ -533,13 +544,14 @@ We defined our email verification page in the `auth.{jsx,tsx}` file.
Users can request a password and then they'll receive an e-mail with a link to reset their password. Users can request a password and then they'll receive an e-mail with a link to reset their password.
Some of the behavior you get out of the box: Some of the behavior you get out of the box:
1. Rate limiting 1. Rate limiting
We are limiting the rate of sign-up requests to **1 request per minute** per email address. This is done to prevent spamming. We are limiting the rate of sign-up requests to **1 request per minute** per email address. This is done to prevent spamming.
2. Preventing user email leaks 2. Preventing user email leaks
If somebody requests a password reset with an unknown email address, we'll give back the same response as if the user requested a password reset successfully. This is done to prevent leaking information. If somebody requests a password reset with an unknown email address, we'll give back the same response as if the user requested a password reset successfully. This is done to prevent leaking information.
Our setup in `main.wasp` looks like this: Our setup in `main.wasp` looks like this:
@ -553,6 +565,7 @@ passwordReset: {
clientRoute: PasswordResetRoute, clientRoute: PasswordResetRoute,
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -563,6 +576,7 @@ passwordReset: {
clientRoute: PasswordResetRoute, clientRoute: PasswordResetRoute,
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -683,6 +697,7 @@ export const signup = async (args, _context) => {
} }
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -718,7 +733,10 @@ type CustomSignupOutput = {
message: string message: string
} }
export const signup: CustomSignup<CustomSignupInput, CustomSignupOutput> = async (args, _context) => { export const signup: CustomSignup<
CustomSignupInput,
CustomSignupOutput
> = async (args, _context) => {
ensureValidEmail(args) ensureValidEmail(args)
ensurePasswordIsPresent(args) ensurePasswordIsPresent(args)
ensureValidPassword(args) ensureValidPassword(args)
@ -728,45 +746,48 @@ export const signup: CustomSignup<CustomSignupInput, CustomSignupOutput> = async
const existingAuthIdentity = await findAuthIdentity(providerId) const existingAuthIdentity = await findAuthIdentity(providerId)
if (existingAuthIdentity) { if (existingAuthIdentity) {
const providerData = deserializeAndSanitizeProviderData<'email'>(existingAuthIdentity.providerData) const providerData = deserializeAndSanitizeProviderData<'email'>(
existingAuthIdentity.providerData
)
// Your custom code here // Your custom code here
} else { } else {
// sanitizeAndSerializeProviderData will hash the user's password // sanitizeAndSerializeProviderData will hash the user's password
const newUserProviderData = await sanitizeAndSerializeProviderData<'email'>({ const newUserProviderData =
await sanitizeAndSerializeProviderData<'email'>({
hashedPassword: args.password, hashedPassword: args.password,
isEmailVerified: false, isEmailVerified: false,
emailVerificationSentAt: null, emailVerificationSentAt: null,
passwordResetSentAt: null, passwordResetSentAt: null,
}) })
await createUser( await createUser(
providerId, providerId,
providerData, providerData,
// Any additional data you want to store on the User entity // Any additional data you want to store on the User entity
{}, {}
) )
// Verification link links to a client route e.g. /email-verification // Verification link links to a client route e.g. /email-verification
const verificationLink = await createEmailVerificationLink(args.email, '/email-verification'); const verificationLink = await createEmailVerificationLink(
args.email,
'/email-verification'
)
try { try {
await sendEmailVerificationEmail( await sendEmailVerificationEmail(args.email, {
args.email, from: {
{ name: 'My App Postman',
from: { email: 'hello@itsme.com',
name: "My App Postman", },
email: "hello@itsme.com", to: args.email,
}, subject: 'Verify your email',
to: args.email, text: `Click the link below to verify your email: ${verificationLink}`,
subject: "Verify your email", html: `
text: `Click the link below to verify your email: ${verificationLink}`,
html: `
<p>Click the link below to verify your email</p> <p>Click the link below to verify your email</p>
<a href="${verificationLink}">Verify email</a> <a href="${verificationLink}">Verify email</a>
`, `,
} })
);
} catch (e: unknown) { } catch (e: unknown) {
console.error("Failed to send email verification email:", e); console.error('Failed to send email verification email:', e)
throw new HttpError(500, "Failed to send email verification email."); throw new HttpError(500, 'Failed to send email verification email.')
} }
} }
} catch (e) { } catch (e) {
@ -785,6 +806,7 @@ export const signup: CustomSignup<CustomSignupInput, CustomSignupOutput> = async
} }
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -875,6 +897,7 @@ model User {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -914,6 +937,7 @@ app myApp {
// ... // ...
} }
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -946,6 +970,7 @@ app myApp {
// ... // ...
} }
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -956,16 +981,20 @@ app myApp {
Read more about the `userSignupFields` function [here](./overview#1-defining-extra-fields). Read more about the `userSignupFields` function [here](./overview#1-defining-extra-fields).
#### `fromField: EmailFromField` <Required /> #### `fromField: EmailFromField` <Required />
`fromField` is a dict that specifies the name and e-mail address of the sender of the e-mails sent by your app. `fromField` is a dict that specifies the name and e-mail address of the sender of the e-mails sent by your app.
It has the following fields: It has the following fields:
- `name`: name of the sender - `name`: name of the sender
- `email`: e-mail address of the sender <Required /> - `email`: e-mail address of the sender <Required />
#### `emailVerification: EmailVerificationConfig` <Required /> #### `emailVerification: EmailVerificationConfig` <Required />
`emailVerification` is a dict that specifies the details of the e-mail verification process. `emailVerification` is a dict that specifies the details of the e-mail verification process.
It has the following fields: It has the following fields:
- `clientRoute: Route`: a route that is used for the user to verify their e-mail address. <Required /> - `clientRoute: Route`: a route that is used for the user to verify their e-mail address. <Required />
Client route should handle the process of taking a token from the URL and sending it to the server to verify the e-mail address. You can use our `verifyEmail` action for that. Client route should handle the process of taking a token from the URL and sending it to the server to verify the e-mail address. You can use our `verifyEmail` action for that.
@ -978,6 +1007,7 @@ It has the following fields:
... ...
await verifyEmail({ token }); await verifyEmail({ token });
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -986,11 +1016,12 @@ It has the following fields:
... ...
await verifyEmail({ token }); await verifyEmail({ token });
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
:::note :::note
We used Auth UI above to avoid doing this work of sending the token to the server manually. We used Auth UI above to avoid doing this work of sending the token to the server manually.
::: :::
- `getEmailContentFn: ExtImport`: a function that returns the content of the e-mail that is sent to the user. - `getEmailContentFn: ExtImport`: a function that returns the content of the e-mail that is sent to the user.
@ -1010,6 +1041,7 @@ It has the following fields:
`, `,
}) })
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -1027,19 +1059,21 @@ It has the following fields:
`, `,
}) })
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
<small>This is the default content of the e-mail, you can customize it to your liking.</small> <small>This is the default content of the e-mail, you can customize it to your liking.</small>
#### `passwordReset: PasswordResetConfig` <Required /> #### `passwordReset: PasswordResetConfig` <Required />
`passwordReset` is a dict that specifies the password reset process. `passwordReset` is a dict that specifies the password reset process.
It has the following fields: It has the following fields:
- `clientRoute: Route`: a route that is used for the user to reset their password. <Required /> - `clientRoute: Route`: a route that is used for the user to reset their password. <Required />
Client route should handle the process of taking a token from the URL and a new password from the user and sending it to the server. You can use our `requestPasswordReset` and `resetPassword` actions to do that. Client route should handle the process of taking a token from the URL and a new password from the user and sending it to the server. You can use our `requestPasswordReset` and `resetPassword` actions to do that.
<Tabs groupId="js-ts"> <Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript"> <TabItem value="js" label="JavaScript">
@ -1055,6 +1089,7 @@ It has the following fields:
... ...
await resetPassword({ password, token }) await resetPassword({ password, token })
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -1069,6 +1104,7 @@ It has the following fields:
... ...
await resetPassword({ password, token }) await resetPassword({ password, token })
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
@ -1093,6 +1129,7 @@ It has the following fields:
`, `,
}) })
``` ```
</TabItem> </TabItem>
<TabItem value="ts" label="TypeScript"> <TabItem value="ts" label="TypeScript">
@ -1110,6 +1147,7 @@ It has the following fields:
`, `,
}) })
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>

View File

@ -410,7 +410,7 @@ function MainPage() {
#### `getFirstProviderUserId` #### `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. 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"> <Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript"> <TabItem value="js" label="JavaScript">
@ -470,7 +470,7 @@ For example, you might set it to `User`:
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {

View File

@ -11,10 +11,9 @@ import WaspFileStructureNote from './\_wasp-file-structure-note.md';
import GetUserFieldsType from './\_getuserfields-type.md'; import GetUserFieldsType from './\_getuserfields-type.md';
import ApiReferenceIntro from './\_api-reference-intro.md'; import ApiReferenceIntro from './\_api-reference-intro.md';
import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md'; import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md';
import DiscordData from '../entities/_discord-data.md'; import DiscordData from '../entities/\_discord-data.md';
import AccessingUserDataNote from '../\_accessing-user-data-note.md'; import AccessingUserDataNote from '../\_accessing-user-data-note.md';
Wasp supports Discord Authentication out of the box. Wasp supports Discord Authentication out of the box.
Letting your users log in using their Discord accounts turns the signup process into a breeze. Letting your users log in using their Discord accounts turns the signup process into a breeze.
@ -43,7 +42,7 @@ Let's start by properly configuring the Auth object:
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -68,7 +67,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -137,8 +136,9 @@ width="400px"
/> />
4. Go to the **OAuth2** tab on the sidebar and click **Add Redirect** 4. Go to the **OAuth2** tab on the sidebar and click **Add Redirect**
- For development, put: `http://localhost:3001/auth/discord/callback`.
- Once you know on which URL your API server will be deployed, you can create a new app with that URL instead e.g. `https://your-server-url.com/auth/discord/callback`. - For development, put: `http://localhost:3001/auth/discord/callback`.
- Once you know on which URL your API server will be deployed, you can create a new app with that URL instead e.g. `https://your-server-url.com/auth/discord/callback`.
4. Hit **Save Changes**. 4. Hit **Save Changes**.
5. Hit **Reset Secret**. 5. Hit **Reset Secret**.
@ -213,9 +213,9 @@ export function Login() {
// A layout component to center the content // A layout component to center the content
export function Layout({ children }) { export function Layout({ children }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@ -241,9 +241,9 @@ export function Login() {
// A layout component to center the content // A layout component to center the content
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@ -263,7 +263,6 @@ Yay, we've successfully set up Discord Auth! 🎉
![Discord Auth](/img/auth/discord.png) ![Discord Auth](/img/auth/discord.png)
Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication. Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication.
To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview). To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview).
@ -277,7 +276,7 @@ Add `discord: {}` to the `auth.methods` dictionary to use it with default settin
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -297,7 +296,7 @@ app myApp {
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -362,7 +361,7 @@ For an up to date info about the data received from Discord, please refer to the
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -394,12 +393,12 @@ model User {
export const userSignupFields = { export const userSignupFields = {
username: (data) => data.profile.global_name, username: (data) => data.profile.global_name,
avatarUrl: (data) => data.profile.avatar, avatarUrl: (data) => data.profile.avatar,
}; }
export function getConfig() { export function getConfig() {
return { return {
scopes: ['identify'], scopes: ['identify'],
}; }
} }
``` ```
@ -409,7 +408,7 @@ export function getConfig() {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -477,7 +476,7 @@ When you receive the `user` object [on the client or the server](../overview.md#
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -501,7 +500,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {

View File

@ -11,7 +11,7 @@ import WaspFileStructureNote from './\_wasp-file-structure-note.md';
import GetUserFieldsType from './\_getuserfields-type.md'; import GetUserFieldsType from './\_getuserfields-type.md';
import ApiReferenceIntro from './\_api-reference-intro.md'; import ApiReferenceIntro from './\_api-reference-intro.md';
import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md'; import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md';
import GithubData from '../entities/_github-data.md'; import GithubData from '../entities/\_github-data.md';
import AccessingUserDataNote from '../\_accessing-user-data-note.md'; import AccessingUserDataNote from '../\_accessing-user-data-note.md';
Wasp supports Github Authentication out of the box. Wasp supports Github Authentication out of the box.
@ -43,7 +43,7 @@ Let's start by properly configuring the Auth object:
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -68,7 +68,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -213,9 +213,9 @@ export function Login() {
// A layout component to center the content // A layout component to center the content
export function Layout({ children }) { export function Layout({ children }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@ -241,9 +241,9 @@ export function Login() {
// A layout component to center the content // A layout component to center the content
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@ -276,7 +276,7 @@ Add `gitHub: {}` to the `auth.methods` dictionary to use it with default setting
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -296,7 +296,7 @@ app myApp {
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -339,7 +339,7 @@ The data we receive from GitHub on the `/user` endpoint looks something this:
"id": 1, "id": 1,
"name": "monalisa octocat", "name": "monalisa octocat",
"avatar_url": "https://github.com/images/error/octocat_happy.gif", "avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "", "gravatar_id": ""
// ... // ...
} }
``` ```
@ -374,7 +374,7 @@ For an up to date info about the data received from GitHub, please refer to the
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -404,14 +404,14 @@ model User {
```js title=src/auth/github.js ```js title=src/auth/github.js
export const userSignupFields = { export const userSignupFields = {
username: () => "hardcoded-username", username: () => 'hardcoded-username',
displayName: (data) => data.profile.name, displayName: (data) => data.profile.name,
}; }
export function getConfig() { export function getConfig() {
return { return {
scopes: ['user'], scopes: ['user'],
}; }
} }
``` ```
@ -421,7 +421,7 @@ export function getConfig() {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -453,7 +453,7 @@ model User {
import { defineUserSignupFields } from 'wasp/server/auth' import { defineUserSignupFields } from 'wasp/server/auth'
export const userSignupFields = defineUserSignupFields({ export const userSignupFields = defineUserSignupFields({
username: () => "hardcoded-username", username: () => 'hardcoded-username',
displayName: (data: any) => data.profile.name, displayName: (data: any) => data.profile.name,
}) })
@ -489,7 +489,7 @@ When you receive the `user` object [on the client or the server](../overview.md#
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -513,7 +513,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {

View File

@ -11,7 +11,7 @@ import WaspFileStructureNote from './\_wasp-file-structure-note.md';
import GetUserFieldsType from './\_getuserfields-type.md'; import GetUserFieldsType from './\_getuserfields-type.md';
import ApiReferenceIntro from './\_api-reference-intro.md'; import ApiReferenceIntro from './\_api-reference-intro.md';
import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md'; import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md';
import GoogleData from '../entities/_google-data.md'; import GoogleData from '../entities/\_google-data.md';
import AccessingUserDataNote from '../\_accessing-user-data-note.md'; import AccessingUserDataNote from '../\_accessing-user-data-note.md';
Wasp supports Google Authentication out of the box. Wasp supports Google Authentication out of the box.
@ -43,7 +43,7 @@ Let's start by properly configuring the Auth object:
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -66,7 +66,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -252,9 +252,9 @@ export function Login() {
// A layout component to center the content // A layout component to center the content
export function Layout({ children }) { export function Layout({ children }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@ -280,9 +280,9 @@ export function Login() {
// A layout component to center the content // A layout component to center the content
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@ -317,7 +317,7 @@ Add `google: {}` to the `auth.methods` dictionary to use it with default setting
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -337,7 +337,7 @@ app myApp {
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -400,7 +400,7 @@ For an up to date info about the data received from Google, please refer to the
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -430,7 +430,7 @@ model User {
```js title=src/auth/google.js ```js title=src/auth/google.js
export const userSignupFields = { export const userSignupFields = {
username: () => "hardcoded-username", username: () => 'hardcoded-username',
displayName: (data) => data.profile.name, displayName: (data) => data.profile.name,
} }
@ -447,7 +447,7 @@ export function getConfig() {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -479,7 +479,7 @@ model User {
import { defineUserSignupFields } from 'wasp/server/auth' import { defineUserSignupFields } from 'wasp/server/auth'
export const userSignupFields = defineUserSignupFields({ export const userSignupFields = defineUserSignupFields({
username: () => "hardcoded-username", username: () => 'hardcoded-username',
displayName: (data: any) => data.profile.name, displayName: (data: any) => data.profile.name,
}) })
@ -515,7 +515,7 @@ When you receive the `user` object [on the client or the server](../overview.md#
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -539,7 +539,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {

View File

@ -11,7 +11,7 @@ import WaspFileStructureNote from './\_wasp-file-structure-note.md';
import GetUserFieldsType from './\_getuserfields-type.md'; import GetUserFieldsType from './\_getuserfields-type.md';
import ApiReferenceIntro from './\_api-reference-intro.md'; import ApiReferenceIntro from './\_api-reference-intro.md';
import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md'; import UserSignupFieldsExplainer from '../\_user-signup-fields-explainer.md';
import KeycloakData from '../entities/_keycloak-data.md'; import KeycloakData from '../entities/\_keycloak-data.md';
import AccessingUserDataNote from '../\_accessing-user-data-note.md'; import AccessingUserDataNote from '../\_accessing-user-data-note.md';
Wasp supports Keycloak Authentication out of the box. Wasp supports Keycloak Authentication out of the box.
@ -42,7 +42,7 @@ Let's start by properly configuring the Auth object:
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -65,7 +65,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -125,19 +125,19 @@ model User {
1. Log into your Keycloak admin console. 1. Log into your Keycloak admin console.
1. Under **Clients**, click on **Create Client**. 1. Under **Clients**, click on **Create Client**.
![Keycloak Screenshot 1](/img/auth/keycloak/1-keycloak.png) ![Keycloak Screenshot 1](/img/auth/keycloak/1-keycloak.png)
1. Fill in the **Client ID** and choose a name for the client. 1. Fill in the **Client ID** and choose a name for the client.
![Keycloak Screenshot 2](/img/auth/keycloak/2-keycloak.png) ![Keycloak Screenshot 2](/img/auth/keycloak/2-keycloak.png)
1. In the next step, enable **Client Authentication**. 1. In the next step, enable **Client Authentication**.
![Keycloak Screenshot 3](/img/auth/keycloak/3-keycloak.png) ![Keycloak Screenshot 3](/img/auth/keycloak/3-keycloak.png)
1. Under **Valid Redirect URIs**, add `http://localhost:3001/auth/keycloak/callback` for local development. 1. Under **Valid Redirect URIs**, add `http://localhost:3001/auth/keycloak/callback` for local development.
![Keycloak Screenshot 4](/img/auth/keycloak/4-keycloak.png) ![Keycloak Screenshot 4](/img/auth/keycloak/4-keycloak.png)
- Once you know on which URL(s) your API server will be deployed, also add those URL(s). - Once you know on which URL(s) your API server will be deployed, also add those URL(s).
- For example: `https://my-server-url.com/auth/keycloak/callback`. - For example: `https://my-server-url.com/auth/keycloak/callback`.
@ -145,7 +145,7 @@ model User {
1. Click **Save**. 1. Click **Save**.
1. In the **Credentials** tab, copy the **Client Secret** value, which we'll use in the next step. 1. In the **Credentials** tab, copy the **Client Secret** value, which we'll use in the next step.
![Keycloak Screenshot 5](/img/auth/keycloak/5-keycloak.png) ![Keycloak Screenshot 5](/img/auth/keycloak/5-keycloak.png)
### 4. Adding Environment Variables ### 4. Adding Environment Variables
@ -220,9 +220,9 @@ export function Login() {
// A layout component to center the content // A layout component to center the content
export function Layout({ children }) { export function Layout({ children }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@ -248,9 +248,9 @@ export function Login() {
// A layout component to center the content // A layout component to center the content
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
<div className="w-full h-full bg-white"> <div className="h-full w-full bg-white">
<div className="min-w-full min-h-[75vh] flex items-center justify-center"> <div className="flex min-h-[75vh] min-w-full items-center justify-center">
<div className="w-full h-full max-w-sm p-5 bg-white"> <div className="h-full w-full max-w-sm bg-white p-5">
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@ -283,7 +283,7 @@ Add `keycloak: {}` to the `auth.methods` dictionary to use it with default setti
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -303,7 +303,7 @@ app myApp {
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -359,7 +359,7 @@ For up-to-date info about the data received from Keycloak, please refer to the [
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -389,7 +389,7 @@ model User {
```js title=src/auth/keycloak.js ```js title=src/auth/keycloak.js
export const userSignupFields = { export const userSignupFields = {
username: () => "hardcoded-username", username: () => 'hardcoded-username',
displayName: (data) => data.profile.name, displayName: (data) => data.profile.name,
} }
@ -406,7 +406,7 @@ export function getConfig() {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -438,7 +438,7 @@ model User {
import { defineUserSignupFields } from 'wasp/server/auth' import { defineUserSignupFields } from 'wasp/server/auth'
export const userSignupFields = defineUserSignupFields({ export const userSignupFields = defineUserSignupFields({
username: () => "hardcoded-username", username: () => 'hardcoded-username',
displayName: (data: any) => data.profile.name, displayName: (data: any) => data.profile.name,
}) })
@ -474,7 +474,7 @@ When you receive the `user` object [on the client or the server](../overview.md#
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -498,7 +498,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {

View File

@ -36,7 +36,7 @@ Here's what the full setup looks like:
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -63,7 +63,7 @@ model User {
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -148,7 +148,7 @@ Declare an import under `app.auth.methods.google.userSignupFields` (the example
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -180,7 +180,7 @@ export const userSignupFields = {
```wasp title=main.wasp ```wasp title=main.wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {

View File

@ -45,7 +45,7 @@ Let's start with adding the following to our `main.wasp` file:
```wasp title="main.wasp" {11} ```wasp title="main.wasp" {11}
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -66,7 +66,7 @@ app myApp {
```wasp title="main.wasp" {11} ```wasp title="main.wasp" {11}
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -624,7 +624,7 @@ When you receive the `user` object [on the client or the server](./overview.md#a
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -649,7 +649,7 @@ model User {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -681,7 +681,7 @@ model User {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {
@ -703,7 +703,7 @@ app myApp {
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
auth: { auth: {

View File

@ -73,7 +73,7 @@ We can start by running `wasp new tasksCrudApp` and then adding the following to
```wasp title="main.wasp" ```wasp title="main.wasp"
app tasksCrudApp { app tasksCrudApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "Tasks Crud App", title: "Tasks Crud App",

View File

@ -66,7 +66,7 @@ Finally, Prisma models become Wasp Entities which can be then used in the `main.
```wasp title="main.wasp" ```wasp title="main.wasp"
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
} }

View File

@ -17,6 +17,7 @@ It is an opinionated way of building **full-stack web applications**. It takes c
major parts of a web application: **client** (front-end), **server** (back-end) and **database**. major parts of a web application: **client** (front-end), **server** (back-end) and **database**.
### Works well with your existing stack ### Works well with your existing stack
Wasp is not trying to do everything at once but rather focuses on the complexity that arises from connecting all the parts of the stack (client, server, database, deployment). Wasp is not trying to do everything at once but rather focuses on the complexity that arises from connecting all the parts of the stack (client, server, database, deployment).
Wasp is using **React**, **Node.js** and **Prisma** under the hood and relies on them to define web components and server queries and actions. Wasp is using **React**, **Node.js** and **Prisma** under the hood and relies on them to define web components and server queries and actions.
@ -35,6 +36,7 @@ At the core is the Wasp compiler which takes the Wasp config and your Javascript
The cool thing about having a compiler that understands your code is that it can do a lot of things for you. The cool thing about having a compiler that understands your code is that it can do a lot of things for you.
Define your app in the Wasp config and get: Define your app in the Wasp config and get:
- login and signup with Auth UI components, - login and signup with Auth UI components,
- full-stack type safety, - full-stack type safety,
- e-mail sending, - e-mail sending,
@ -52,10 +54,11 @@ Let's say you want to build a web app that allows users to **create and share th
Let's start with the `main.wasp` file: it is the central file of your app, where you describe the app from the high level. Let's start with the `main.wasp` file: it is the central file of your app, where you describe the app from the high level.
Let's give our app a title and let's immediately turn on the full-stack authentication via username and password: Let's give our app a title and let's immediately turn on the full-stack authentication via username and password:
```wasp title="main.wasp" ```wasp title="main.wasp"
app RecipeApp { app RecipeApp {
title: "My Recipes", title: "My Recipes",
wasp: { version: "^0.14.0" }, wasp: { version: "^0.15.0" },
auth: { auth: {
methods: { usernameAndPassword: {} }, methods: { usernameAndPassword: {} },
onAuthFailedRedirectTo: "/login", onAuthFailedRedirectTo: "/login",
@ -90,6 +93,7 @@ We do that by defining Operations, in this case, a Query `getRecipes` and Action
which are in their essence Node.js functions that execute on the server and can, thanks to Wasp, very easily be called from the client. which are in their essence Node.js functions that execute on the server and can, thanks to Wasp, very easily be called from the client.
First, we define these Operations in our `main.wasp` file, so Wasp knows about them and can "beef them up": First, we define these Operations in our `main.wasp` file, so Wasp knows about them and can "beef them up":
```wasp title="main.wasp" ```wasp title="main.wasp"
// Queries have automatic cache invalidation and are type-safe. // Queries have automatic cache invalidation and are type-safe.
query getRecipes { query getRecipes {
@ -125,6 +129,7 @@ Now we can very easily use these in our React components!
For the end, let's create a home page of our app. For the end, let's create a home page of our app.
First, we define it in `main.wasp`: First, we define it in `main.wasp`:
```wasp title="main.wasp" ```wasp title="main.wasp"
... ...
@ -138,30 +143,32 @@ page HomePage {
and then implement it as a React component in JS/TS (that calls the Operations we previously defined): and then implement it as a React component in JS/TS (that calls the Operations we previously defined):
```tsx title="src/pages/HomePage.tsx" ```tsx title="src/pages/HomePage.tsx"
import { useQuery, getRecipes } from "wasp/client/operations"; import { useQuery, getRecipes } from 'wasp/client/operations'
import { type User } from "wasp/entities"; import { type User } from 'wasp/entities'
export function HomePage({ user }: { user: User }) { export function HomePage({ user }: { user: User }) {
// Due to full-stack type safety, `recipes` will be of type `Recipe[]` here. // Due to full-stack type safety, `recipes` will be of type `Recipe[]` here.
const { data: recipes, isLoading } = useQuery(getRecipes); // Calling our query here! const { data: recipes, isLoading } = useQuery(getRecipes) // Calling our query here!
if (isLoading) { if (isLoading) {
return <div>Loading...</div>; return <div>Loading...</div>
} }
return ( return (
<div> <div>
<h1>Recipes</h1> <h1>Recipes</h1>
<ul> <ul>
{recipes ? recipes.map((recipe) => ( {recipes
<li key={recipe.id}> ? recipes.map((recipe) => (
<div>{recipe.title}</div> <li key={recipe.id}>
<div>{recipe.description}</div> <div>{recipe.title}</div>
</li> <div>{recipe.description}</div>
)) : 'No recipes defined yet!'} </li>
))
: 'No recipes defined yet!'}
</ul> </ul>
</div> </div>
); )
} }
``` ```
@ -174,14 +181,17 @@ Above we skipped defining `/login` and `/signup` pages to keep the example a bit
::: :::
## When to use Wasp ## When to use Wasp
Wasp addresses the same core problems that typical web app frameworks are addressing, and it in big part [looks, swims and quacks](https://en.wikipedia.org/wiki/Duck_test) like a web app framework. Wasp addresses the same core problems that typical web app frameworks are addressing, and it in big part [looks, swims and quacks](https://en.wikipedia.org/wiki/Duck_test) like a web app framework.
### Best used for ### Best used for
- building full-stack web apps (like e.g. Airbnb or Asana) - building full-stack web apps (like e.g. Airbnb or Asana)
- quickly starting a web app with industry best practices - quickly starting a web app with industry best practices
- to be used alongside modern web dev stack (React and Node.js are currently supported) - to be used alongside modern web dev stack (React and Node.js are currently supported)
### Avoid using Wasp for ### Avoid using Wasp for
- building static/presentational websites - building static/presentational websites
- to be used as a no-code solution - to be used as a no-code solution
- to be a solve-it-all tool in a single language - to be a solve-it-all tool in a single language
@ -196,7 +206,7 @@ Wasp does not match typical expectations of a web app framework: it is not a set
Wasp is a programming language, but a specific kind: it is specialized for a single purpose: **building modern web applications**. We call such languages *DSL*s (Domain Specific Language). Wasp is a programming language, but a specific kind: it is specialized for a single purpose: **building modern web applications**. We call such languages *DSL*s (Domain Specific Language).
Other examples of *DSL*s that are often used today are e.g. *SQL* for databases and *HTML* for web page layouts. Other examples of *DSL*s that are often used today are e.g. _SQL_ for databases and _HTML_ for web page layouts.
The main advantage and reason why *DSL*s exist is that they need to do only one task (e.g. database queries) The main advantage and reason why *DSL*s exist is that they need to do only one task (e.g. database queries)
so they can do it well and provide the best possible experience for the developer. so they can do it well and provide the best possible experience for the developer.

View File

@ -9,7 +9,7 @@ Each Wasp project can have only one `app` type declaration. It is used to config
```wasp ```wasp
app todoApp { app todoApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "ToDo App", title: "ToDo App",
head: [ head: [
@ -27,7 +27,7 @@ You may want to change the title of your app, which appears in the browser tab,
```wasp ```wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "BookFace" title: "BookFace"
} }
@ -42,7 +42,7 @@ An example of adding extra style sheets and scripts:
```wasp ```wasp
app myApp { app myApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "My App", title: "My App",
head: [ // optional head: [ // optional
@ -58,7 +58,7 @@ app myApp {
```wasp ```wasp
app todoApp { app todoApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "ToDo App", title: "ToDo App",
head: [ head: [

View File

@ -93,7 +93,7 @@ The default `main.wasp` file generated with `wasp new` on the previous page look
```wasp title="main.wasp" ```wasp title="main.wasp"
app TodoApp { app TodoApp {
wasp: { wasp: {
version: "^0.14.0" // Pins the version of Wasp to use. version: "^0.15.0" // Pins the version of Wasp to use.
}, },
title: "TodoApp" // Used as the browser tab title. Note that all strings in Wasp are double quoted! title: "TodoApp" // Used as the browser tab title. Note that all strings in Wasp are double quoted!
} }
@ -113,7 +113,7 @@ page MainPage {
```wasp title="main.wasp" ```wasp title="main.wasp"
app TodoApp { app TodoApp {
wasp: { wasp: {
version: "^0.14.0" // Pins the version of Wasp to use. version: "^0.15.0" // Pins the version of Wasp to use.
}, },
title: "TodoApp" // Used as the browser tab title. Note that all strings in Wasp are double quoted! title: "TodoApp" // Used as the browser tab title. Note that all strings in Wasp are double quoted!
} }

View File

@ -194,7 +194,7 @@ Your Wasp file should now look like this:
```wasp title="main.wasp" ```wasp title="main.wasp"
app TodoApp { app TodoApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "TodoApp" title: "TodoApp"
} }
@ -211,7 +211,7 @@ page MainPage {
```wasp title="main.wasp" ```wasp title="main.wasp"
app TodoApp { app TodoApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
title: "TodoApp" title: "TodoApp"
} }

View File

@ -38,7 +38,7 @@ Next, tell Wasp to use full-stack [authentication](../auth/overview):
```wasp title="main.wasp" ```wasp title="main.wasp"
app TodoApp { app TodoApp {
wasp: { wasp: {
version: "^0.14.0" version: "^0.15.0"
}, },
// highlight-start // highlight-start
title: "TodoApp", title: "TodoApp",
@ -275,7 +275,6 @@ src={useBaseUrl('img/wasp_user_in_db.gif')}
style={{ border: "1px solid black" }} style={{ border: "1px solid black" }}
/> />
You'll notice that we now have a `User` entity in the database alongside the `Task` entity. 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 you haven't yet updated the queries and actions to have per-user tasks. Let's do that next. 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 you haven't yet updated the queries and actions to have per-user tasks. Let's do that next.