-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add edgedb auth helper for svelte projects (#839)
* Add edgedb auth helper for svelte projects * Delete code meant for testing * Use Sveltekit instead Svelte throughout the library * Remove old auth-svelte from git * Add svelte & vite deps to package.json * Update global lock file. * Add cookies as SvelteServerAuth class property Refactor and simplify class methods to get only form data as a parameter. Update this.cookies when cookies needs a change. * Refactor: return authRouteHook & serverAuth object * Update function and type names * Rename top level server auth function * Update classes and functions naming * Support FormData in server auth handlers Throw if auth route handler doesn't call redirect * Memoize session getter * Update readme * Update session getter * Update readme with change to app.d.ts * Update release.yml with auth-sveltekit * Update readme
- Loading branch information
Showing
7 changed files
with
1,999 additions
and
645 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"name": "@edgedb/auth-sveltekit", | ||
"description": "Helper library to integrate the EdgeDB Auth extension with Sveltekit.", | ||
"version": "0.1.0-alpha.1", | ||
"author": "EdgeDB <[email protected]>", | ||
"type": "module", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/edgedb/edgedb-js.git", | ||
"directory": "packages/auth-sveltekit" | ||
}, | ||
"license": "Apache-2.0", | ||
"sideEffects": false, | ||
"files": [ | ||
"/dist" | ||
], | ||
"scripts": { | ||
"build": "tsc --project tsconfig.json" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.8.4", | ||
"edgedb": "^1.3.6", | ||
"typescript": "^5.2.2", | ||
"svelte": "^4.2.7", | ||
"@sveltejs/kit": "^2.0.0", | ||
"vite": "^5.0.3" | ||
}, | ||
"peerDependencies": { | ||
"edgedb": "^1.3.6" | ||
|
||
}, | ||
"dependencies": { | ||
"@edgedb/auth-core": "^0.1.0-beta.1" | ||
}, | ||
"exports": { | ||
"./*": "./dist/*.js" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# @edgedb/auth-sveltekit | ||
|
||
This library provides a set of utilities to help you integrate authentication into your [Sveltekit](https://kit.svelte.dev/) application. | ||
It supports authentication with various OAuth providers, as well as email/password authentication. | ||
|
||
## Installation | ||
|
||
This package is published on npm and can be installed with your package manager of choice. | ||
|
||
```bash | ||
npm install @edgedb/auth-sveltekit | ||
``` | ||
|
||
## Setup | ||
|
||
**Prerequisites**: Before adding EdgeDB auth to your Sveltekit app, you will first need to enable the `auth` extension in your EdgeDB schema, and have configured the extension with some providers (you can do this in CLI or EdgeDB UI). Refer to the auth extension docs for details on how to do this. | ||
|
||
### Client auth helper | ||
|
||
Initialize the client auth helper by passing configuration options to `createClientAuth()`. This will return a `ClientAuth` object which you can use in your components. You can skip this part if you find it unnecessary and provide all your data through the load functions (the next step), but we suggest having the client auth too and use it directly in your components to get OAuth, BuiltinUI and signout URLs. | ||
|
||
```ts | ||
// src/lib/auth.ts | ||
|
||
import createClientAuth, { | ||
type AuthOptions, | ||
} from "@edgedb/auth-sveltekit/client"; | ||
|
||
export const options: AuthOptions = { | ||
baseUrl: "http://localhost:5173", | ||
// ... | ||
}; | ||
|
||
const auth = createClientAuth(options); | ||
|
||
export default auth; | ||
``` | ||
|
||
The available auth config options are as follows: | ||
|
||
- `baseUrl: string`, _required_, The url of your application; needed for various auth flows (eg. OAuth) to redirect back to. | ||
- `authRoutesPath?: string`, The path to the auth route handlers, defaults to `'auth'`, see below for more details. | ||
- `authCookieName?: string`, The name of the cookie where the auth token will be stored, defaults to `'edgedb-session'`. | ||
- `pkceVerifierCookieName?: string`: The name of the cookie where the verifier for the PKCE flow will be stored, defaults to `'edgedb-pkce-verifier'` | ||
- `passwordResetUrl?: string`: The url of the the password reset page; needed if you want to enable password reset emails in your app. | ||
| ||
|
||
### EdgeDB client | ||
|
||
Lets create an EdgeDB client that you will need for creating server auth client. | ||
|
||
```ts | ||
// src/lib/server/auth.ts | ||
import { createClient } from "edgedb"; | ||
|
||
export const client = createClient({ | ||
//Note: when developing locally you will need to set tls security to insecure, because the dev server uses self-signed certificates which will cause api calls with the fetch api to fail. | ||
tlsSecurity: "insecure", | ||
}); | ||
``` | ||
|
||
### Server auth client | ||
|
||
Create the server auth client in a handle hook. Firstly call `serverAuth` passing to it EdgeDB client you created in the previous step, along with configuration options from step 1. This will give you back the `createServerRequestAuth` and `createAuthRouteHook`. You should call `createServerRequestAuth` inside a handle to attach the server client to `event.locals`. Calling `createAuthRouteHook` will give you back a hook so you should just invoke it inside `sequence` and pass your auth route handlers to it. | ||
You can now access the server auth in all actions and load functions through `event.locals`. | ||
|
||
```ts | ||
import serverAuth, { | ||
type AuthRouteHandlers, | ||
} from "@edgedb/auth-sveltekit/server"; | ||
import { redirect, type Handle } from "@sveltejs/kit"; | ||
import { sequence } from "@sveltejs/kit/hooks"; | ||
import { client } from "$lib/server/auth"; | ||
import { createUser } from "$lib/server/utils"; | ||
import { options } from "$lib/auth"; | ||
|
||
const { createServerRequestAuth, createAuthRouteHook } = serverAuth( | ||
client, | ||
options | ||
); | ||
|
||
const createServerAuthClient: Handle = ({ event, resolve }) => { | ||
event.locals.auth = createServerRequestAuth(event); // (*) | ||
|
||
return resolve(event); | ||
}; | ||
|
||
// You only need to configure callback functions for the types of auth you wish to use in your app. (**) | ||
const authRouteHandlers: AuthRouteHandlers = { | ||
async onOAuthCallback({ error, tokenData, provider, isSignUp }) { | ||
redirect(303, "/"); | ||
}, | ||
onSignout() { | ||
redirect("/"); | ||
}, | ||
}; | ||
|
||
export const handle = sequence( | ||
createServerAuthClient, | ||
createAuthRouteHook(authRouteHandlers) | ||
); | ||
``` | ||
|
||
\* If you use typescript you need to update `Locals` type with `auth` so that auth is correctly recognized throughout the project: | ||
|
||
```ts | ||
import type { ServerRequestAuth } from "@edgedb/auth-sveltekit/server"; | ||
|
||
declare global { | ||
namespace App { | ||
// interface Error {} | ||
interface Locals { | ||
auth: ServerRequestAuth; | ||
} | ||
// interface PageData {} | ||
// interface PageState {} | ||
// interface Platform {} | ||
} | ||
} | ||
|
||
export {}; | ||
``` | ||
|
||
\*\* The currently available auth route handlers are: | ||
|
||
- `onOAuthCallback` | ||
- `onBuiltinUICallback` | ||
- `onEmailVerify` | ||
- `onSignout` | ||
|
||
In any of them you can define what to do in case of success or error. Every handler should return a redirect call. | ||
|
||
### UI | ||
|
||
Now we just need to setup the UI to allow your users to sign in/up, etc. The easiest way to get started is to use the EdgeDB Auth's builtin UI. Or alternatively you can implement your own custom UI. | ||
|
||
**Builtin UI** | ||
|
||
To use the builtin auth UI, first you will need to enable the UI in the auth ext configuration (see the auth ext docs for details). For the `redirect_to` and `redirect_to_on_signup` configuration options, set them to `{your_app_url}/auth/builtin/callback` and `{your_app_url}/auth/builtin/callback?isSignUp=true` respectively. (Note: if you have setup the auth route handlers under a custom path, replace `auth` in the above url with that path). | ||
|
||
Then you just need to configure the `onBuiltinUICallback` route handler to define what to do once the builtin ui redirects back to your app, and place a link to the builtin UI url returned by `auth.getBuiltinUIUrl()` somewhere in your app. | ||
|
||
**Custom UI** | ||
|
||
To help with implementing your own custom auth UI, the `auth` object has a number of methods you can use: | ||
|
||
- `emailPasswordSignUp(data: { email: string; password: string } | FormData)` | ||
- `emailPasswordSignIn(data: { email: string; password: string } | FormData)` | ||
- `emailPasswordResendVerificationEmail(data: { verification_token: string } | FormData)` | ||
- `emailPasswordSendPasswordResetEmail(data: { email: string } | FormData)` | ||
- `emailPasswordResetPassword(data: { reset_token: string; password: string } | FormData)` | ||
- `signout()` | ||
- `isPasswordResetTokenValid(resetToken: string)`: Checks if a password reset token is still valid. | ||
- `getOAuthUrl(providerName: string)`: This method takes the name of an OAuth provider (make sure you configure providers you need in the auth ext config first using CLI or EdgeDB UI) and returns a link that will initiate the OAuth sign in flow for that provider. You will also need to configure the `onOAuthCallback` auth route handler. | ||
|
||
## Usage | ||
|
||
Now you have auth configured and users can signin/signup/etc. You can use the `locals.auth.session` in server files to retrieve an `AuthSession` object. This session object allows you to check if the user is currently signed in with the `isSignedIn` method, and also provides a `Client` object automatically configured with the `ext::auth::client_token` global, so you can run queries using the `ext::auth::ClientTokenIdentity` of the currently signed in user. | ||
|
||
```ts | ||
// src/routes/+page.server.ts | ||
import { fail, type Actions } from "@sveltejs/kit"; | ||
|
||
export async function load({ locals }) { | ||
const session = locals.auth.session; | ||
const isSignedIn = await session.isSignedIn(); | ||
|
||
return { | ||
isSignedIn, | ||
}; | ||
} | ||
``` | ||
|
||
```svelte | ||
<!-- src/routes/+page.svelte --> | ||
<script> | ||
import clientAuth from "$lib/auth"; | ||
export let data; | ||
</script> | ||
<div> | ||
{#if data.isSignedIn} | ||
<h2>You are logged in!</h2> | ||
{:else} | ||
<h2>You are not logged in.</h2> | ||
<a href={clientAuth.getBuiltinUIUrl()}>Sign in with Builtin UI</a> | ||
{/if} | ||
</div> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import type { BuiltinOAuthProviderNames } from "@edgedb/auth-core"; | ||
|
||
export interface AuthOptions { | ||
baseUrl: string; | ||
authRoutesPath?: string; | ||
authCookieName?: string; | ||
pkceVerifierCookieName?: string; | ||
passwordResetPath?: string; | ||
} | ||
|
||
type OptionalOptions = "passwordResetPath"; | ||
|
||
export type AuthConfig = Required<Omit<AuthOptions, OptionalOptions>> & | ||
Pick<AuthOptions, OptionalOptions> & { authRoute: string }; | ||
|
||
export function getConfig(options: AuthOptions) { | ||
const baseUrl = options.baseUrl.replace(/\/$/, ""); | ||
const authRoutesPath = | ||
options.authRoutesPath?.replace(/^\/|\/$/g, "") ?? "auth"; | ||
|
||
return { | ||
baseUrl, | ||
authRoutesPath, | ||
authCookieName: options.authCookieName ?? "edgedb-session", | ||
pkceVerifierCookieName: | ||
options.pkceVerifierCookieName ?? "edgedb-pkce-verifier", | ||
passwordResetPath: options.passwordResetPath, | ||
authRoute: `${baseUrl}/${authRoutesPath}`, | ||
}; | ||
} | ||
|
||
export default function createClientAuth(options: AuthOptions) { | ||
return new ClientAuth(options); | ||
} | ||
|
||
export class ClientAuth { | ||
protected readonly config: AuthConfig; | ||
|
||
/** @internal */ | ||
constructor(options: AuthOptions) { | ||
this.config = getConfig(options); | ||
} | ||
|
||
getOAuthUrl(providerName: BuiltinOAuthProviderNames) { | ||
return `${this.config.authRoute}/oauth?${new URLSearchParams({ | ||
provider_name: providerName, | ||
}).toString()}`; | ||
} | ||
|
||
getBuiltinUIUrl() { | ||
return `${this.config.authRoute}/builtin/signin`; | ||
} | ||
|
||
getBuiltinUISignUpUrl() { | ||
return `${this.config.authRoute}/builtin/signup`; | ||
} | ||
|
||
getSignoutUrl() { | ||
return `${this.config.authRoute}/signout`; | ||
} | ||
} |
Oops, something went wrong.