@edgedb/auth-sveltekit
Advanced tools
+5
-1
| import type { BuiltinOAuthProviderNames } from "@edgedb/auth-core"; | ||
| import { WebAuthnClient } from "@edgedb/auth-core/webauthn"; | ||
| export interface AuthOptions { | ||
@@ -8,4 +9,5 @@ baseUrl: string; | ||
| passwordResetPath?: string; | ||
| magicLinkFailurePath?: string; | ||
| } | ||
| type OptionalOptions = "passwordResetPath"; | ||
| type OptionalOptions = "passwordResetPath" | "magicLinkFailurePath"; | ||
| export type AuthConfig = Required<Omit<AuthOptions, OptionalOptions>> & Pick<AuthOptions, OptionalOptions> & { | ||
@@ -20,2 +22,3 @@ authRoute: string; | ||
| passwordResetPath: string | undefined; | ||
| magicLinkFailurePath: string | undefined; | ||
| authRoute: string; | ||
@@ -27,2 +30,3 @@ }; | ||
| protected readonly isSecure: boolean; | ||
| readonly webAuthnClient: WebAuthnClient; | ||
| /** @internal */ | ||
@@ -29,0 +33,0 @@ constructor(options: AuthOptions); |
+10
-0
@@ -0,1 +1,2 @@ | ||
| import { WebAuthnClient } from "@edgedb/auth-core/webauthn"; | ||
| export function getConfig(options) { | ||
@@ -10,2 +11,3 @@ const baseUrl = options.baseUrl.replace(/\/$/, ""); | ||
| passwordResetPath: options.passwordResetPath, | ||
| magicLinkFailurePath: options.magicLinkFailurePath, | ||
| authRoute: `${baseUrl}/${authRoutesPath}`, | ||
@@ -20,2 +22,3 @@ }; | ||
| isSecure; | ||
| webAuthnClient; | ||
| /** @internal */ | ||
@@ -25,2 +28,9 @@ constructor(options) { | ||
| this.isSecure = this.config.baseUrl.startsWith("https"); | ||
| this.webAuthnClient = new WebAuthnClient({ | ||
| signupOptionsUrl: `${this.config.authRoute}/webauthn/signup/options`, | ||
| signupUrl: `${this.config.authRoute}/webauthn/signup`, | ||
| signinOptionsUrl: `${this.config.authRoute}/webauthn/signin/options`, | ||
| signinUrl: `${this.config.authRoute}/webauthn/signin`, | ||
| verifyUrl: `${this.config.authRoute}/webauthn/verify`, | ||
| }); | ||
| } | ||
@@ -27,0 +37,0 @@ getOAuthUrl(providerName) { |
+29
-3
| import { type RequestEvent, type Handle } from "@sveltejs/kit"; | ||
| import type { Client } from "edgedb"; | ||
| import { Auth, type BuiltinOAuthProviderNames, type TokenData, type emailPasswordProviderName } from "@edgedb/auth-core"; | ||
| import { Auth, type BuiltinOAuthProviderNames, type TokenData, type emailPasswordProviderName, type AuthenticationResponseJSON, type RegistrationResponseJSON } from "@edgedb/auth-core"; | ||
| import { ClientAuth, type AuthOptions } from "./client.js"; | ||
@@ -37,2 +37,6 @@ export * from "@edgedb/auth-core/errors"; | ||
| }>) => Promise<never>; | ||
| onMagicLinkCallback(params: ParamsOrError<{ | ||
| tokenData: TokenData; | ||
| isSignUp: boolean; | ||
| }>): Promise<Response>; | ||
| onSignout?: () => Promise<never>; | ||
@@ -49,2 +53,6 @@ } | ||
| private _session; | ||
| private setVerifierCookie; | ||
| private setAuthCookie; | ||
| private deleteVerifierCookie; | ||
| private deleteAuthCookie; | ||
| get session(): AuthSession; | ||
@@ -54,4 +62,2 @@ /** @internal */ | ||
| isPasswordResetTokenValid(resetToken: string): boolean | null; | ||
| private setVerifierCookie; | ||
| private setAuthCookie; | ||
| getProvidersInfo(): Promise<{ | ||
@@ -91,2 +97,22 @@ oauth: { | ||
| }>; | ||
| magicLinkSignUp(data: { | ||
| email: string; | ||
| } | FormData): Promise<void>; | ||
| magicLinkSend(data: { | ||
| email: string; | ||
| } | FormData): Promise<void>; | ||
| webAuthnSignIn(data: { | ||
| email: string; | ||
| assertion: AuthenticationResponseJSON; | ||
| }): Promise<{ | ||
| tokenData: TokenData; | ||
| }>; | ||
| webAuthnSignUp(data: { | ||
| email: string; | ||
| credentials: RegistrationResponseJSON; | ||
| verify_url: string; | ||
| user_handle: string; | ||
| }): Promise<{ | ||
| tokenData: TokenData | null; | ||
| }>; | ||
| signout(): Promise<void>; | ||
@@ -93,0 +119,0 @@ } |
+171
-59
| import { redirect, } from "@sveltejs/kit"; | ||
| import { Auth, builtinOAuthProviderNames, BackendError, ConfigurationError, PKCEError, InvalidDataError, OAuthProviderFailureError, EdgeDBAuthError, } from "@edgedb/auth-core"; | ||
| import { Auth, builtinOAuthProviderNames, BackendError, ConfigurationError, PKCEError, InvalidDataError, OAuthProviderFailureError, EdgeDBAuthError, MagicLinkFailureError, } from "@edgedb/auth-core"; | ||
| import { ClientAuth, getConfig, } from "./client.js"; | ||
@@ -23,2 +23,27 @@ export * from "@edgedb/auth-core/errors"; | ||
| } | ||
| const BASE_COOKIE_CONFIG = { | ||
| httpOnly: true, | ||
| sameSite: "lax", | ||
| path: "/", | ||
| }; | ||
| function setVerifierCookie(cookies, config, value) { | ||
| const expires = new Date(Date.now() + 1000 * 60 * 24 * 7); // in 7 days | ||
| cookies.set(config.pkceVerifierCookieName, value, { | ||
| ...BASE_COOKIE_CONFIG, | ||
| expires, | ||
| secure: config.baseUrl.startsWith("https"), | ||
| }); | ||
| } | ||
| function setAuthCookie(cookies, config, value) { | ||
| cookies.set(config.authCookieName, value, { | ||
| ...BASE_COOKIE_CONFIG, | ||
| expires: Auth.getTokenExpiration(value) || undefined, | ||
| secure: config.baseUrl.startsWith("https"), | ||
| }); | ||
| } | ||
| function deleteCookie(cookies, name) { | ||
| cookies.set(name, "", { | ||
| path: "/", | ||
| }); | ||
| } | ||
| export class ServerRequestAuth extends ClientAuth { | ||
@@ -29,2 +54,14 @@ client; | ||
| _session; | ||
| setVerifierCookie(verifier) { | ||
| setVerifierCookie(this.cookies, this.config, verifier); | ||
| } | ||
| setAuthCookie(authToken) { | ||
| setAuthCookie(this.cookies, this.config, authToken); | ||
| } | ||
| deleteVerifierCookie() { | ||
| deleteCookie(this.cookies, this.config.pkceVerifierCookieName); | ||
| } | ||
| deleteAuthCookie() { | ||
| deleteCookie(this.cookies, this.config.authCookieName); | ||
| } | ||
| get session() { | ||
@@ -46,22 +83,2 @@ if (!this._session) { | ||
| } | ||
| setVerifierCookie(verifier) { | ||
| const expires = new Date(Date.now() + 1000 * 60 * 24 * 7); // In 7 days | ||
| this.cookies.set(this.config.pkceVerifierCookieName, verifier, { | ||
| httpOnly: true, | ||
| sameSite: "lax", | ||
| path: "/", | ||
| expires, | ||
| secure: this.isSecure, | ||
| }); | ||
| } | ||
| setAuthCookie(authToken) { | ||
| const expires = Auth.getTokenExpiration(authToken); | ||
| this.cookies.set(this.config.authCookieName, authToken, { | ||
| httpOnly: true, | ||
| sameSite: "lax", | ||
| path: "/", | ||
| expires: expires ?? undefined, | ||
| secure: this.isSecure, | ||
| }); | ||
| } | ||
| async getProvidersInfo() { | ||
@@ -125,9 +142,40 @@ return (await this.core).getProvidersInfo(); | ||
| this.setAuthCookie(tokenData.auth_token); | ||
| this.cookies.delete(this.config.pkceVerifierCookieName, { | ||
| path: "/", | ||
| }); | ||
| this.deleteVerifierCookie(); | ||
| return { tokenData }; | ||
| } | ||
| async magicLinkSignUp(data) { | ||
| if (!this.config.magicLinkFailurePath) { | ||
| throw new ConfigurationError(`'magicLinkFailurePath' option not configured`); | ||
| } | ||
| const [email] = extractParams(data, ["email"], "email missing"); | ||
| const { verifier } = await (await this.core).signupWithMagicLink(email, `${this.config.authRoute}/magiclink/callback?isSignUp=true`, new URL(this.config.magicLinkFailurePath, this.config.baseUrl).toString()); | ||
| this.setVerifierCookie(verifier); | ||
| } | ||
| async magicLinkSend(data) { | ||
| if (!this.config.magicLinkFailurePath) { | ||
| throw new ConfigurationError(`'magicLinkFailurePath' option not configured`); | ||
| } | ||
| const [email] = extractParams(data, ["email"], "email missing"); | ||
| const { verifier } = await (await this.core).signinWithMagicLink(email, `${this.config.authRoute}/magiclink/callback?isSignUp=true`, new URL(this.config.magicLinkFailurePath, this.config.baseUrl).toString()); | ||
| this.setVerifierCookie(verifier); | ||
| } | ||
| async webAuthnSignIn(data) { | ||
| const { email, assertion } = data; | ||
| const tokenData = await (await this.core).signinWithWebAuthn(email, assertion); | ||
| this.setAuthCookie(tokenData.auth_token); | ||
| return { tokenData }; | ||
| } | ||
| async webAuthnSignUp(data) { | ||
| const { email, credentials, verify_url, user_handle } = data; | ||
| const result = await (await this.core).signupWithWebAuthn(email, credentials, verify_url, user_handle); | ||
| this.setVerifierCookie(result.verifier); | ||
| if (result.status === "complete") { | ||
| const tokenData = result.tokenData; | ||
| this.setAuthCookie(tokenData.auth_token); | ||
| return { tokenData }; | ||
| } | ||
| return { tokenData: null }; | ||
| } | ||
| async signout() { | ||
| this.cookies.delete(this.config.authCookieName, { path: "/" }); | ||
| this.deleteAuthCookie(); | ||
| } | ||
@@ -184,3 +232,3 @@ } | ||
| } | ||
| async function handleAuthRoutes({ onOAuthCallback, onBuiltinUICallback, onEmailVerify, onSignout, }, { url, cookies }, core, config) { | ||
| async function handleAuthRoutes({ onOAuthCallback, onBuiltinUICallback, onEmailVerify, onMagicLinkCallback, onSignout, }, { url, cookies }, core, config) { | ||
| const searchParams = url.searchParams; | ||
@@ -199,7 +247,4 @@ const path = url.pathname.split("/").slice(2).join("/"); | ||
| const pkceSession = await core.then((core) => core.createPKCESession()); | ||
| cookies.set(config.pkceVerifierCookieName, pkceSession.verifier, { | ||
| httpOnly: true, | ||
| path: "/", | ||
| }); | ||
| return redirect(303, pkceSession.getOAuthUrl(provider, redirectUrl, `${redirectUrl}?isSignUp=true`)); | ||
| setVerifierCookie(cookies, config, pkceSession.verifier); | ||
| return redirect(307, pkceSession.getOAuthUrl(provider, redirectUrl, `${redirectUrl}?isSignUp=true`)); | ||
| } | ||
@@ -239,11 +284,4 @@ case "oauth/callback": { | ||
| } | ||
| cookies.set(config.authCookieName, tokenData.auth_token, { | ||
| httpOnly: true, | ||
| sameSite: "lax", | ||
| path: "/", | ||
| }); | ||
| cookies.set(config.pkceVerifierCookieName, "", { | ||
| maxAge: 0, | ||
| path: "/", | ||
| }); | ||
| setAuthCookie(cookies, config, tokenData.auth_token); | ||
| deleteCookie(cookies, config.pkceVerifierCookieName); | ||
| return onOAuthCallback({ | ||
@@ -298,11 +336,4 @@ error: null, | ||
| } | ||
| cookies.set(config.authCookieName, tokenData.auth_token, { | ||
| httpOnly: true, | ||
| sameSite: "strict", | ||
| path: "/", | ||
| }); | ||
| cookies.set(config.pkceVerifierCookieName, "", { | ||
| maxAge: 0, | ||
| path: "/", | ||
| }); | ||
| setAuthCookie(cookies, config, tokenData.auth_token); | ||
| deleteCookie(cookies, config.pkceVerifierCookieName); | ||
| return onBuiltinUICallback({ | ||
@@ -318,7 +349,4 @@ error: null, | ||
| const pkceSession = await core.then((core) => core.createPKCESession()); | ||
| cookies.set(config.pkceVerifierCookieName, pkceSession.verifier, { | ||
| httpOnly: true, | ||
| path: "/", | ||
| }); | ||
| return redirect(303, path.split("/").pop() === "signup" | ||
| deleteCookie(cookies, config.pkceVerifierCookieName); | ||
| return redirect(307, path.split("/").pop() === "signup" | ||
| ? pkceSession.getHostedUISignupUrl() | ||
@@ -354,7 +382,91 @@ : pkceSession.getHostedUISigninUrl()); | ||
| } | ||
| cookies.set(config.authCookieName, tokenData.auth_token, { | ||
| httpOnly: true, | ||
| sameSite: "strict", | ||
| path: "/", | ||
| setAuthCookie(cookies, config, tokenData.auth_token); | ||
| return onEmailVerify({ | ||
| error: null, | ||
| tokenData, | ||
| }); | ||
| } | ||
| case "magiclink/callback": { | ||
| if (!onMagicLinkCallback) { | ||
| throw new ConfigurationError(`'onMagicLinkCallback' auth route handler not configured`); | ||
| } | ||
| const error = searchParams.get("error"); | ||
| if (error) { | ||
| const desc = searchParams.get("error_description"); | ||
| return onMagicLinkCallback({ | ||
| error: new MagicLinkFailureError(error + (desc ? `: ${desc}` : "")), | ||
| }); | ||
| } | ||
| const code = searchParams.get("code"); | ||
| const isSignUp = searchParams.get("isSignUp") === "true"; | ||
| const verifier = cookies.get(config.pkceVerifierCookieName); | ||
| if (!code) { | ||
| return onMagicLinkCallback({ | ||
| error: new PKCEError("no pkce code in response"), | ||
| }); | ||
| } | ||
| if (!verifier) { | ||
| return onMagicLinkCallback({ | ||
| error: new PKCEError("no pkce verifier cookie found"), | ||
| }); | ||
| } | ||
| let tokenData; | ||
| try { | ||
| tokenData = await (await core).getToken(code, verifier); | ||
| } | ||
| catch (err) { | ||
| return onMagicLinkCallback({ | ||
| error: err instanceof Error ? err : new Error(String(err)), | ||
| }); | ||
| } | ||
| setAuthCookie(cookies, config, tokenData.auth_token); | ||
| deleteCookie(cookies, config.pkceVerifierCookieName); | ||
| return onMagicLinkCallback({ | ||
| error: null, | ||
| tokenData, | ||
| isSignUp, | ||
| }); | ||
| } | ||
| case "webauthn/signup/options": { | ||
| const email = searchParams.get("email"); | ||
| if (!email) { | ||
| throw new InvalidDataError("email missing"); | ||
| } | ||
| return redirect(307, (await core).getWebAuthnSignupOptionsUrl(email)); | ||
| } | ||
| case "webauthn/signin/options": { | ||
| const email = searchParams.get("email"); | ||
| if (!email) { | ||
| throw new InvalidDataError("email missing"); | ||
| } | ||
| return redirect(307, (await core).getWebAuthnSigninOptionsUrl(email)); | ||
| } | ||
| case "webauthn/verify": { | ||
| if (!onEmailVerify) { | ||
| throw new ConfigurationError(`'onEmailVerify' auth route handler not configured`); | ||
| } | ||
| const verificationToken = searchParams.get("verification_token"); | ||
| const verifier = cookies.get(config.pkceVerifierCookieName); | ||
| if (!verificationToken) { | ||
| return onEmailVerify({ | ||
| error: new PKCEError("no verification_token in response"), | ||
| }); | ||
| } | ||
| if (!verifier) { | ||
| return onEmailVerify({ | ||
| error: new PKCEError("no pkce verifier cookie found"), | ||
| verificationToken, | ||
| }); | ||
| } | ||
| let tokenData; | ||
| try { | ||
| tokenData = await (await core).verifyWebAuthnSignup(verificationToken, verifier); | ||
| } | ||
| catch (err) { | ||
| return onEmailVerify({ | ||
| error: err instanceof Error ? err : new Error(String(err)), | ||
| verificationToken, | ||
| }); | ||
| } | ||
| setAuthCookie(cookies, config, tokenData.auth_token); | ||
| return onEmailVerify({ | ||
@@ -369,3 +481,3 @@ error: null, | ||
| } | ||
| cookies.delete(config.authCookieName, { path: "/" }); | ||
| deleteCookie(cookies, config.authCookieName); | ||
| return onSignout(); | ||
@@ -372,0 +484,0 @@ } |
+1
-1
| { | ||
| "name": "@edgedb/auth-sveltekit", | ||
| "description": "Helper library to integrate the EdgeDB Auth extension with Sveltekit.", | ||
| "version": "0.3.0-canary.20240510T202738", | ||
| "version": "0.3.0-canary.20240521T175926", | ||
| "author": "EdgeDB <info@edgedb.com>", | ||
@@ -6,0 +6,0 @@ "type": "module", |
35809
22.79%683
28.63%