Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(providers): Add Loops Email Provider and Documentation #11197

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
89f5b4e
init loops config
Whats-A-MattR Jun 16, 2024
f17ef2f
feat(providers): add loops.so email provider
Whats-A-MattR Jun 17, 2024
375b05d
Merge branch 'main' of https://github.com/Whats-A-MattR/next-auth
Whats-A-MattR Jun 17, 2024
0a8b697
feat(providers): update to include Loops as Email Provider
Whats-A-MattR Jun 18, 2024
4cd943e
feat(docs): documentation for new Loops.so Email Provider
Whats-A-MattR Jun 18, 2024
ce6380f
chore(docs): change loops.so to loops
Whats-A-MattR Jun 18, 2024
8c9b43c
Merge branch 'main' into main
Whats-A-MattR Jun 18, 2024
25be0ea
Merge branch 'main' into main
Whats-A-MattR Jun 24, 2024
353cd9a
Merge branch 'main' into main
Whats-A-MattR Jul 13, 2024
d863cf0
Merge branch 'main' into main
ndom91 Jul 21, 2024
4deb94b
fix: update loops.svg - rm instrinsic size
ndom91 Jul 21, 2024
3f6e190
fix: update loops.mdx rm svg height
ndom91 Jul 21, 2024
8375b85
fix(docs): update loops.mdx image size
ndom91 Jul 21, 2024
01a6e48
fix(docs): updating for Svelte environment imports
Whats-A-MattR Jul 23, 2024
9eead5c
fix(docs): updating for Svelte environment imports
Whats-A-MattR Jul 23, 2024
51bdd22
Merge branch 'main' into main
Whats-A-MattR Jul 23, 2024
46f3a6a
Merge branch 'main' into main
Whats-A-MattR Jul 29, 2024
7886e9b
Merge branch 'main' into main
Whats-A-MattR Aug 5, 2024
4bc2648
Merge branch 'main' into main
Whats-A-MattR Aug 11, 2024
3ec1f97
Merge branch 'main' into main
Whats-A-MattR Aug 15, 2024
facb2aa
Merge branch 'main' into main
Whats-A-MattR Aug 19, 2024
8f1542f
Merge branch 'main' into main
Whats-A-MattR Aug 21, 2024
3a4536a
Merge branch 'main' into main
Whats-A-MattR Aug 23, 2024
f48cffd
Merge branch 'main' into main
ndom91 Aug 23, 2024
15b44cf
Updated type for LoopsConfig, making transactionId a requirement
Whats-A-MattR Aug 26, 2024
585e161
Merge branch 'main' into main
Whats-A-MattR Aug 26, 2024
8d7af3f
Merge branch 'main' into main
ndom91 Aug 28, 2024
05036fd
chore(types): refactored params in sendVerificationRequest to an expo…
Whats-A-MattR Sep 5, 2024
7c7f3ab
fix(types): fixed issue with typings that prevented builds
Whats-A-MattR Sep 5, 2024
2242667
forgot to reimplement options to facilitate extension of sendVerifica…
Whats-A-MattR Sep 5, 2024
608a060
(fix): transactionalId not being passed to sendVerificationRequest func
Whats-A-MattR Sep 5, 2024
519bfd9
Merge branch 'main' into main
Whats-A-MattR Sep 5, 2024
31cbd62
Merge branch 'main' into main
Whats-A-MattR Sep 7, 2024
ceab1d0
Merge branch 'main' into email-docs-fix
Whats-A-MattR Sep 8, 2024
6390e5b
fix(docs): resolve merge conflicts
Whats-A-MattR Sep 8, 2024
ddb57d9
Update email.mdx
Whats-A-MattR Sep 8, 2024
a1436b2
Merge branch 'main' into email-docs-fix
Whats-A-MattR Sep 11, 2024
f8d9fd9
Update loops.ts
ndom91 Sep 11, 2024
1cdb820
Update loops.ts
ndom91 Sep 11, 2024
8916000
Merge branch 'main' into email-docs-fix
Whats-A-MattR Sep 12, 2024
7844bd7
Update email.ts
Whats-A-MattR Sep 21, 2024
5c728a2
Merge branch 'main' into email-docs-fix
Whats-A-MattR Sep 21, 2024
e0d3817
Update packages/core/src/providers/email.ts
ndom91 Sep 22, 2024
bbdebe1
Merge branch 'main' into email-docs-fix
ndom91 Sep 22, 2024
73368fb
fix: prettier
ndom91 Sep 22, 2024
d1caaf7
fix(docs): prettier
ndom91 Sep 22, 2024
fd24abd
Merge branch 'main' into email-docs-fix
ndom91 Sep 22, 2024
742731f
fix(provider): reimplemented extended/customized types to facilitate …
Whats-A-MattR Sep 23, 2024
5b82a42
Merge branch 'main' into email-docs-fix
Whats-A-MattR Oct 4, 2024
da17716
Merge branch 'main' into email-docs-fix
Whats-A-MattR Oct 7, 2024
8d761f4
Merge branch 'main' into email-docs-fix
Whats-A-MattR Oct 11, 2024
6611ec0
Merge branch 'main' into email-docs-fix
Whats-A-MattR Oct 12, 2024
13a95ee
Merge branch 'main' into email-docs-fix
Whats-A-MattR Oct 14, 2024
d12136d
Merge branch 'main' into email-docs-fix
Whats-A-MattR Oct 16, 2024
baf9212
Merge branch 'main' into email-docs-fix
Whats-A-MattR Oct 28, 2024
f60c044
Merge branch 'main' into email-docs-fix
Whats-A-MattR Oct 29, 2024
6f12e9a
Merge branch 'main' into email-docs-fix
Whats-A-MattR Nov 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/2_bug_provider.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ body:
- "Kinde"
- "Line"
- "LinkedIn"
- "Loops"
- "Mailchimp"
- "Mail.ru"
- "Mastodon"
Expand Down
149 changes: 148 additions & 1 deletion docs/pages/getting-started/authentication/email.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ This login mechanism starts by the user providing their email address at the log
>
<img className="size-16" src={`/img/providers/postmark.svg`} />
<div className="text-sm text-center">Postmark</div>
</RichTabs.Trigger>
<RichTabs.Trigger
value="loops"
orientation="vertical"
className="!border border-neutral-200 dark:border-neutral-700 aria-selected:!bg-neutral-100 aria-selected:dark:!bg-neutral-950 dark:!bg-neutral-900 !bg-white !h-auto !w-auto !gap-2 !justify-start p-6 px-8 rounded-md outline-none transition-all duration-300 hover:bg-neutral-200 !font-normal"
>
<img className="size-16" src={`/img/providers/loops.svg`} />
<div className="text-sm text-center">Loops</div>
</RichTabs.Trigger>
<RichTabs.Trigger
value="mailgun"
Expand Down Expand Up @@ -1039,6 +1047,145 @@ Start your application, once the user enters their Email and clicks on the signi

For more information on this provider go to the [Postmark docs page](/getting-started/providers/postmark).

</RichTabs.Content>
<RichTabs.Content
orientation="vertical"
value="loops"
className="h-full !border-0 !w-full"
tabIndex={-1}>

### Loops Setup

<Steps>

### Database Adapter

Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier,
a database is required for passwordless login to work as verification tokens need to be stored.

### Create your Transactional Email Template on Loops

Loops have provided a super handy [guide](https://loops.so/docs/transactional/guide) to help you get started with creating your transactional email template.
This provider only passes one data varaiable into the template, `url` which is the magic link to sign in. This is case sensitive, so make sure you use `url` in your template. <br/>
On the last page of Template creation, you'll need to copy the `TRANSACTIONAL ID`. If you skipped this step, don't worry, you can get this at any from the Template edit page.

### Create an API Key on Loops

You'll need to create an API key to authenticate with Loops. This key should be kept secret and not shared with anyone.
You can Generate a key by going to the [API Settings Page](https://app.loops.so/settings?page=api) and clicking Generate.
You should name the key something that makes sense to you, like "Auth.js".

### Setup Environment Variables

To implement Loops, you need to set up the following environment variables. You should have these from the previous steps.

```bash filename=".env"
AUTH_LOOPS_KEY=abc123
AUTH_LOOPS_TRANSACTIONAL_ID=def456
```

### Setup Provider

Let's enable `Loops` as a sign-in option for our Auth.js configuration. You'll have to import the `Loops` provider from the package and pass it to the providers array we set up earlier in the Auth.js config file:

<Code>
<Code.Next>

```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Loops from "next-auth/providers/loops"

export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Loops({
apiKey: process.env.AUTH_LOOPS_KEY,
transactionalId: process.env.AUTH_LOOPS_TRANSACTIONAL_ID,
}),
],
})
```

</Code.Next>
<Code.Svelte>

```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Loops from "@auth/sveltekit/providers/loops"
import {
AUTH_LOOPS_KEY,
AUTH_LOOPS_TRANSACTIONAL_ID,
} from "$env/static/private"

export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Loops({
apiKey: AUTH_LOOPS_KEY,
transactionalId: AUTH_LOOPS_TRANSACTIONAL_ID,
}),
],
})
```

```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```

</Code.Svelte>
</Code>

### Add Signin Button

Next, we add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in.

<Code>
<Code.Next>

```tsx filename="./components/sign-in.tsx"
import { signIn } from "../../auth.ts"

export function SignIn() {
return (
<form
action={async (formData) => {
"use server"
await signIn("loops", formData)
}}
>
<input type="text" name="email" placeholder="Email" />
<button type="submit">Sign in with Loops</button>
</form>
)
}
```

</Code.Next>
<Code.Svelte>

```ts filename="src/routes/+page.svelte"

<script lang="ts">
import { SignIn } from "@auth/sveltekit/components"
</script>

<div>
<nav>
<img src="/img/logo.svg" alt="Company Logo" />
<SignIn provider="loops"/>
</nav>
</div>

```

</Code.Svelte>
</Code>

### Signin

Start your application, click on the signin button we just added, and you should see Auth.js built-in sign in page with the option to sign in with your email.
A user can enter their email, click "Sign in with Loops", and receive their beautifully formatted signin email.
Clicking on the link in the email will redirect the user to your application, landing already authenticated!

</Steps>
</RichTabs.Content>
<RichTabs.Content
orientation="vertical"
Expand Down Expand Up @@ -1194,5 +1341,5 @@ Start your application, once the user enters their Email and clicks on the signi
For more information on this provider go to the [Mailgun docs page](/getting-started/providers/mailgun).

</RichTabs.Content>
</RichTabs>
</RichTabs>
</div>
103 changes: 103 additions & 0 deletions docs/pages/getting-started/providers/loops.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"

<img align="right" src="/img/providers/loops.svg" height="96" />

# Loops Provider

## Overview

The Loops provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in.

Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted).

The Loops provider can be used in conjunction with (or instead of) one or more OAuth providers.

## How it works

On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.

If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.

<Callout type="warning">
The Loops provider can be used with both JSON Web Token and database managed
sessions, however **you must configure a database** to use it. It is not
possible to enable email sign in without using a database.
</Callout>

## Configuration

### Add and Verify your Domain on Loops

First, you'll need to have completed the steps covered in the ['Start here'](https://loops.so/docs/start-here) Loops documentation.
The main thing required is to [set up your domain records](https://loops.so/docs/start-here#1-set-up-your-domain-records).

### Generate an API Key

Next, you will have to generate an API key in the [Loops Dashboard](https://loops.so/api-keys). You can save this API key as the `AUTH_LOOPS_KEY` environment variable.

```sh
AUTH_LOOPS_KEY=abc
```

### Create a Transactional Email Template on Loops

The easiest way to achieve this is using the [Loops email editor](https://loops.so/docs/creating-emails/editor) to create a transactional email template.
If you're new to Loops, you can find rich documentation [here](https://loops.so/docs/transactional/guide).

<br />
Copy the Transactional ID value from the last page of the template creation
process, and save this as the `AUTH_LOOPS_TRANSACTIONAL_ID` environment
variable. If you're following these steps, you should now have two environment
variables set up for Loops.

```sh
AUTH_LOOPS_KEY=abc
AUTH_LOOPS_TRANSACTIONAL_ID=def
```

<Callout type="warning">
When creating your email template, make sure to include the `url` variable in
the template. This is the URL that will sent to the user, allowing them to
signin.
</Callout>

<Code>
<Code.Next>
### Configure AuthJS with the Loops Provider
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Loops from "next-auth/providers/loops"

export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ..., // database adapter of your choosing
providers: [
Loops({
apiKey: process.env.AUTH_LOOPS_KEY,
transactionalId: process.env.AUTH_LOOPS_TRANSACTIONAL_ID,
}),
],
})
```

</Code.Next>
<Code.Svelte>
### Configure AuthJS with the Loops Provider
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Loops from "@auth/sveltekit/providers/loops"
import { AUTH_LOOPS_KEY, AUTH_LOOPS_TRANSACTIONAL_ID } from "@env/static/private"

export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ..., // database adapter of your choosing
providers: [
Loops({
apiKey: AUTH_LOOPS_KEY,
transactionalId: AUTH_LOOPS_TRANSACTIONAL_ID,
}),
],
})
```

</Code.Svelte>
</Code>
3 changes: 3 additions & 0 deletions docs/public/img/providers/loops.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 13 additions & 9 deletions packages/core/src/providers/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,25 @@ export default function Email(config: NodemailerUserConfig): NodemailerConfig {
// when started working on https://github.com/nextauthjs/next-auth/discussions/1465
export type EmailProviderType = "email"

export type EmailProviderSendVerificationRequestParams = {
identifier: string
url: string
expires: Date
provider: EmailConfig
token: string
theme: Theme
request: Request
}

export interface EmailConfig extends CommonProviderOptions {
id: string
type: EmailProviderType
name: string
from?: string
maxAge?: number
sendVerificationRequest: (params: {
identifier: string
url: string
expires: Date
provider: EmailConfig
token: string
theme: Theme
request: Request
}) => Awaitable<void>
sendVerificationRequest: (
params: EmailProviderSendVerificationRequestParams
) => Awaitable<void>
/** Used to hash the verification token. */
secret?: string
/** Used with HTTP-based email providers. */
Expand Down
79 changes: 79 additions & 0 deletions packages/core/src/providers/loops.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* <div style={{backgroundColor: "#24292f", display: "flex", justifyContent: "space-between", color: "#fff", padding: 16}}>
* <span>Built-in <b>Loops</b> integration.</span>
* <a href="https://loops.so">
* <img style={{display: "block"}} src="https://authjs.dev/img/providers/loops.svg" width="48"/>
* </a>
* </div>
*
* @module providers/loops
*/

import type { EmailConfig, EmailUserConfig } from "./email.js"

export type LoopsUserConfig = Omit<Partial<LoopsConfig>, "options" | "type">

export interface LoopsConfig
extends Omit<EmailConfig, "sendVerificationRequest" | "options"> {
id: string
apiKey: string
transactionalId: string
sendVerificationRequest: (params: Params) => Promise<void>
options: LoopsUserConfig
}

type Params = Parameters<EmailConfig["sendVerificationRequest"]>[0] & {
provider: LoopsConfig
}

/**
*
* @param config
* @returns LoopsConfig
* @requires LoopsUserConfig
* @example
* ```ts
* Loops({
* apiKey: process.env.AUTH_LOOPS_KEY,
* transactionalId: process.env.AUTH_LOOPS_TRANSACTIONAL_ID,
* })
* ```
*
* @typedef LoopsUserConfig
*/

export default function Loops(config: LoopsUserConfig): LoopsConfig {
return {
id: "loops",
apiKey: "",
type: "email",
name: "Loops",
from: "Auth.js <[email protected]>",
maxAge: 24 * 60 * 60,
transactionalId: config.transactionalId || "",
async sendVerificationRequest(params: Params) {
const { identifier: to, provider, url } = params
if (!provider.apiKey || !provider.transactionalId)
throw new TypeError("Missing Loops API Key or TransactionalId")

const res = await fetch("https://app.loops.so/api/v1/transactional", {
method: "POST",
headers: {
Authorization: `Bearer ${provider.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
transactionalId: provider.transactionalId,
email: to,
dataVariables: {
url: url,
},
}),
})
if (!res.ok) {
throw new Error("Loops Send Error: " + JSON.stringify(await res.json()))
}
},
options: config,
}
}
Loading