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

Add WebAuthn and Magic Links in auth-remix #899

Merged
merged 8 commits into from Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/auth-core/src/errors.ts
Expand Up @@ -88,6 +88,13 @@ export class OAuthProviderFailureError extends UserError {
}
}

/** Magic link flow failed. */
export class MagicLinkFailureError extends UserError {
get type() {
return "MagicLinkFailure";
}
}

/** Error with email verification. */
export class VerificationError extends UserError {
get type() {
Expand Down
6 changes: 2 additions & 4 deletions packages/auth-core/src/webauthn.ts
Expand Up @@ -4,8 +4,6 @@ import { requestGET, requestPOST } from "./utils";
import type {
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON,
SignupResponse,
TokenData,
} from "./types";

interface WebAuthnClientOptions {
Expand All @@ -31,7 +29,7 @@ export class WebAuthnClient {
this.verifyUrl = options.verifyUrl;
}

public async signUp(email: string): Promise<SignupResponse> {
public async signUp<T>(email: string): Promise<T> {
const options = await requestGET<PublicKeyCredentialCreationOptionsJSON>(
this.signupOptionsUrl,
{ email },
Expand Down Expand Up @@ -79,7 +77,7 @@ export class WebAuthnClient {
type: credentials.type,
};

return await requestPOST<SignupResponse>(
return await requestPOST<T>(
this.signupUrl,
{
email,
Expand Down
10 changes: 5 additions & 5 deletions packages/auth-remix/readme.md
Expand Up @@ -41,7 +41,7 @@ export default auth;

import createServerAuth from "@edgedb/auth-remix/server";
import { createClient } from "edgedb";
import { options } from "./auth.client";
import { options } from "./auth";

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.
Expand All @@ -65,13 +65,13 @@ export default auth;
// app/routes/auth.$.ts

import { redirect } from "@remix-run/node";
import { auth } from "~/services/auth.server";
import auth from "~/services/auth.server";

export const { loader } = auth.createAuthRouteHandlers({
onOAuthCallback({ error, tokenData, provider, isSignUp }) {
async onOAuthCallback({ error, tokenData, provider, isSignUp }) {
return redirect("/");
},
onSignout() {
async onSignout() {
return redirect("/");
},
});
Expand Down Expand Up @@ -116,9 +116,9 @@ Now you have auth all configured and user's can signin/signup/etc. you can use t
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData, Link } from "@remix-run/react";

import auth, { client } from "~/services/auth.server";
import clientAuth from "~/services/auth.client";
import { transformSearchParams } from "~/utils";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const session = auth.getSession(request);
Expand Down
19 changes: 14 additions & 5 deletions packages/auth-remix/src/client.ts
@@ -1,14 +1,16 @@
import type { BuiltinOAuthProviderNames } from "@edgedb/auth-core";
import { WebAuthnClient } from "@edgedb/auth-core/webauthn";

export interface RemixAuthOptions {
baseUrl: string;
authRoutesPath?: string;
authCookieName?: string;
pkceVerifierCookieName?: string;
passwordResetPath?: string;
magicLinkFailurePath?: string;
}

type OptionalOptions = "passwordResetPath";
type OptionalOptions = "passwordResetPath" | "magicLinkFailurePath";

export default function createClientAuth(options: RemixAuthOptions) {
return new RemixClientAuth(options);
Expand All @@ -19,17 +21,24 @@ export class RemixClientAuth {
Omit<RemixAuthOptions, OptionalOptions>
> &
Pick<RemixAuthOptions, OptionalOptions>;
readonly webAuthnClient: WebAuthnClient;

/** @internal */
constructor(options: RemixAuthOptions) {
this.options = {
authCookieName: "edgedb-session",
pkceVerifierCookieName: "edgedb-pkce-verifier",
...options,
baseUrl: options.baseUrl.replace(/\/$/, ""),
authRoutesPath: options.authRoutesPath?.replace(/^\/|\/$/g, "") ?? "auth",
authCookieName: options.authCookieName ?? "edgedb-session",
pkceVerifierCookieName:
options.pkceVerifierCookieName ?? "edgedb-pkce-verifier",
passwordResetPath: options.passwordResetPath,
};
this.webAuthnClient = new WebAuthnClient({
signupOptionsUrl: `${this._authRoute}/webauthn/signup/options`,
signupUrl: `${this._authRoute}/webauthn/signup`,
signinOptionsUrl: `${this._authRoute}/webauthn/signin/options`,
signinUrl: `${this._authRoute}/webauthn/signin`,
verifyUrl: `${this._authRoute}/webauthn/verify`,
});
}

protected get _authRoute() {
Expand Down