From 0786f8afb3e8b15a71b44eb954a8d492e8b1cb8d Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 16 Nov 2023 12:51:50 +0000 Subject: [PATCH 1/9] Catch error in `isLoggedIn` when auth token exists but is invalid --- packages/auth-nextjs/src/shared.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/auth-nextjs/src/shared.ts b/packages/auth-nextjs/src/shared.ts index 0b8148dfd..e90eff869 100644 --- a/packages/auth-nextjs/src/shared.ts +++ b/packages/auth-nextjs/src/shared.ts @@ -66,8 +66,12 @@ export class NextAuthSession { async isLoggedIn() { if (!this.authToken) return false; - return (await this.client.querySingle( - `select exists global ext::auth::ClientTokenIdentity` - )) as boolean; + try { + return (await this.client.querySingle( + `select exists global ext::auth::ClientTokenIdentity` + )) as boolean; + } catch { + return false; + } } } From d22c97fe3f0dc698c89a6c46d26894f9923471c1 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 16 Nov 2023 12:57:10 +0000 Subject: [PATCH 2/9] Update `passwordResetUrl` to `passwordResetPath` + actually use it + fix typing --- packages/auth-nextjs/src/app/index.ts | 21 +++++++++++++++------ packages/auth-nextjs/src/shared.ts | 6 +++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/auth-nextjs/src/app/index.ts b/packages/auth-nextjs/src/app/index.ts index df87428af..e217f91a3 100644 --- a/packages/auth-nextjs/src/app/index.ts +++ b/packages/auth-nextjs/src/app/index.ts @@ -350,8 +350,8 @@ export class NextAppAuth extends NextAuth { } } case "emailpassword/send-reset-email": { - if (!this.options.passwordResetUrl) { - throw new Error(`'passwordResetUrl' option not configured`); + if (!this.options.passwordResetPath) { + throw new Error(`'passwordResetPath' option not configured`); } const [email] = _extractParams( await _getReqBody(req), @@ -360,7 +360,13 @@ export class NextAppAuth extends NextAuth { ); const { verifier } = await ( await this.core - ).sendPasswordResetEmail(email, this.options.passwordResetUrl); + ).sendPasswordResetEmail( + email, + new URL( + this.options.passwordResetPath, + this.options.baseUrl + ).toString() + ); cookies().set({ name: this.options.pkceVerifierCookieName, value: verifier, @@ -486,15 +492,18 @@ export class NextAppAuth extends NextAuth { emailPasswordSendPasswordResetEmail: async ( data: FormData | { email: string } ) => { - if (!this.options.passwordResetUrl) { - throw new Error(`'passwordResetUrl' option not configured`); + if (!this.options.passwordResetPath) { + throw new Error(`'passwordResetPath' option not configured`); } const [email] = _extractParams(data, ["email"], "email missing"); const { verifier } = await ( await this.core ).sendPasswordResetEmail( email, - `${this.options.baseUrl}/${this.options.passwordResetUrl}` + new URL( + this.options.passwordResetPath, + this.options.baseUrl + ).toString() ); cookies().set({ name: this.options.pkceVerifierCookieName, diff --git a/packages/auth-nextjs/src/shared.ts b/packages/auth-nextjs/src/shared.ts index e90eff869..1a8b6fe4b 100644 --- a/packages/auth-nextjs/src/shared.ts +++ b/packages/auth-nextjs/src/shared.ts @@ -6,10 +6,10 @@ export interface NextAuthOptions { authRoutesPath?: string; authCookieName?: string; pkceVerifierCookieName?: string; - passwordResetUrl?: string; + passwordResetPath?: string; } -type OptionalOptions = "passwordResetUrl"; +type OptionalOptions = "passwordResetPath"; export abstract class NextAuth { /** @internal */ @@ -24,7 +24,7 @@ export abstract class NextAuth { authCookieName: options.authCookieName ?? "edgedb-session", pkceVerifierCookieName: options.pkceVerifierCookieName ?? "edgedb-pkce-verifier", - passwordResetUrl: options.passwordResetUrl, + passwordResetPath: options.passwordResetPath, }; } From e0359628556ebd55ef71b05288494de9d7206e26 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 16 Nov 2023 12:57:53 +0000 Subject: [PATCH 3/9] Fix type in reset password action params --- packages/auth-nextjs/src/app/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/auth-nextjs/src/app/index.ts b/packages/auth-nextjs/src/app/index.ts index e217f91a3..e3cc95b83 100644 --- a/packages/auth-nextjs/src/app/index.ts +++ b/packages/auth-nextjs/src/app/index.ts @@ -513,7 +513,7 @@ export class NextAppAuth extends NextAuth { }); }, emailPasswordResetPassword: async ( - data: FormData | { resetToken: string; password: string } + data: FormData | { reset_token: string; password: string } ) => { const verifier = cookies().get( this.options.pkceVerifierCookieName From 622dd9931e26d523943c7818e47e843ab1ad53e9 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 16 Nov 2023 12:59:45 +0000 Subject: [PATCH 4/9] Re-export `getProvidersInfo` and some useful types from auth-core in auth-nextjs --- packages/auth-nextjs/src/app/index.ts | 18 ++++++++++++++++-- packages/auth-nextjs/src/shared.ts | 10 +++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/auth-nextjs/src/app/index.ts b/packages/auth-nextjs/src/app/index.ts index e3cc95b83..1c92adbaa 100644 --- a/packages/auth-nextjs/src/app/index.ts +++ b/packages/auth-nextjs/src/app/index.ts @@ -9,9 +9,19 @@ import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import type { NextRequest } from "next/server"; -import { NextAuth, NextAuthSession, type NextAuthOptions } from "../shared"; +import { + NextAuth, + NextAuthSession, + type NextAuthOptions, + BuiltinProviderNames, +} from "../shared"; -export { NextAuthSession, type NextAuthOptions }; +export { + NextAuthSession, + type NextAuthOptions, + type BuiltinProviderNames, + type TokenData, +}; type ParamsOrError = | ({ error: null } & Result) @@ -48,6 +58,10 @@ export class NextAppAuth extends NextAuth { ); } + async getProvidersInfo() { + return (await this.core).getProvidersInfo(); + } + createAuthRouteHandlers({ onOAuthCallback, onEmailPasswordSignIn, diff --git a/packages/auth-nextjs/src/shared.ts b/packages/auth-nextjs/src/shared.ts index 1a8b6fe4b..8563dd33b 100644 --- a/packages/auth-nextjs/src/shared.ts +++ b/packages/auth-nextjs/src/shared.ts @@ -1,5 +1,13 @@ import { Client } from "edgedb"; -import { Auth, BuiltinOAuthProviderNames } from "@edgedb/auth-core"; +import { + Auth, + BuiltinOAuthProviderNames, + emailPasswordProviderName, +} from "@edgedb/auth-core"; + +export type BuiltinProviderNames = + | BuiltinOAuthProviderNames + | typeof emailPasswordProviderName; export interface NextAuthOptions { baseUrl: string; From 9018ebaa25fd5314864261461efb70f0e8918af4 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 16 Nov 2023 13:01:39 +0000 Subject: [PATCH 5/9] Fix verifier cookie not being set if email verification not required causing later verification to fail --- packages/auth-core/src/core.ts | 3 ++- packages/auth-nextjs/src/app/index.ts | 27 +++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/auth-core/src/core.ts b/packages/auth-core/src/core.ts index 3d387b0d1..0d6fb3862 100644 --- a/packages/auth-core/src/core.ts +++ b/packages/auth-core/src/core.ts @@ -106,7 +106,7 @@ export class Auth { password: string, verifyUrl: string ): Promise< - | { status: "complete"; tokenData: TokenData } + | { status: "complete"; verifier: string; tokenData: TokenData } | { status: "verificationRequired"; verifier: string } > { const { challenge, verifier } = await pkce.createVerifierChallengePair(); @@ -122,6 +122,7 @@ export class Auth { if ("code" in result) { return { status: "complete", + verifier, tokenData: await this.getToken(result.code, verifier), }; } else { diff --git a/packages/auth-nextjs/src/app/index.ts b/packages/auth-nextjs/src/app/index.ts index 1c92adbaa..b75ebbc20 100644 --- a/packages/auth-nextjs/src/app/index.ts +++ b/packages/auth-nextjs/src/app/index.ts @@ -342,6 +342,12 @@ export class NextAppAuth extends NextAuth { error: err instanceof Error ? err : new Error(String(err)), }); } + cookies().set({ + name: this.options.pkceVerifierCookieName, + value: result.verifier, + httpOnly: true, + sameSite: "strict", + }); if (result.status === "complete") { cookies().set({ name: this.options.authCookieName, @@ -354,12 +360,6 @@ export class NextAppAuth extends NextAuth { tokenData: result.tokenData, }); } else { - cookies().set({ - name: this.options.pkceVerifierCookieName, - value: result.verifier, - httpOnly: true, - sameSite: "strict", - }); return onEmailPasswordSignUp({ error: null, tokenData: null }); } } @@ -485,6 +485,12 @@ export class NextAppAuth extends NextAuth { password, `${this._authRoute}/emailpassword/verify` ); + cookies().set({ + name: this.options.pkceVerifierCookieName, + value: result.verifier, + httpOnly: true, + sameSite: "strict", + }); if (result.status === "complete") { cookies().set({ name: this.options.authCookieName, @@ -493,15 +499,8 @@ export class NextAppAuth extends NextAuth { sameSite: "strict", }); return result.tokenData; - } else { - cookies().set({ - name: this.options.pkceVerifierCookieName, - value: result.verifier, - httpOnly: true, - sameSite: "strict", - }); - return null; } + return null; }, emailPasswordSendPasswordResetEmail: async ( data: FormData | { email: string } From 508275dc59ae3b3b7a689531c615e065265fed2b Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 16 Nov 2023 13:04:50 +0000 Subject: [PATCH 6/9] Return `verificationToken` on email verification failure, to allow email to be re-sent --- packages/auth-nextjs/src/app/index.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/auth-nextjs/src/app/index.ts b/packages/auth-nextjs/src/app/index.ts index b75ebbc20..1b5e48adf 100644 --- a/packages/auth-nextjs/src/app/index.ts +++ b/packages/auth-nextjs/src/app/index.ts @@ -23,9 +23,9 @@ export { type TokenData, }; -type ParamsOrError = - | ({ error: null } & Result) - | ({ error: Error } & { [Key in keyof Result]?: undefined }); +type ParamsOrError = + | ({ error: null } & { [Key in keyof ErrorDetails]?: undefined } & Result) + | ({ error: Error } & ErrorDetails & { [Key in keyof Result]?: undefined }); export interface CreateAuthRouteHandlers { onOAuthCallback( @@ -36,7 +36,12 @@ export interface CreateAuthRouteHandlers { params: ParamsOrError<{ tokenData: TokenData | null }> ): void; onEmailPasswordReset(params: ParamsOrError<{ tokenData: TokenData }>): void; - onEmailVerify(params: ParamsOrError<{ tokenData: TokenData }>): void; + onEmailVerify( + params: ParamsOrError< + { tokenData: TokenData }, + { verificationToken?: string } + > + ): void; onBuiltinUICallback( params: ParamsOrError<{ tokenData: TokenData | null; isSignUp: boolean }> ): void; @@ -172,6 +177,7 @@ export class NextAppAuth extends NextAuth { if (!verifier) { return onEmailVerify({ error: new Error("no pkce verifier cookie found"), + verificationToken, }); } let tokenData: TokenData; @@ -182,6 +188,7 @@ export class NextAppAuth extends NextAuth { } catch (err) { return onEmailVerify({ error: err instanceof Error ? err : new Error(String(err)), + verificationToken, }); } cookies().set({ From f364c1a2ad6d38d26b9659fd65466c2a6480cb2a Mon Sep 17 00:00:00 2001 From: James Clarke Date: Thu, 16 Nov 2023 13:07:02 +0000 Subject: [PATCH 7/9] Return the `provider` to `onOAuthCallback` and `onBuiltinUICallback` --- packages/auth-nextjs/src/app/index.ts | 35 ++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/auth-nextjs/src/app/index.ts b/packages/auth-nextjs/src/app/index.ts index 1b5e48adf..1598129ef 100644 --- a/packages/auth-nextjs/src/app/index.ts +++ b/packages/auth-nextjs/src/app/index.ts @@ -29,7 +29,11 @@ type ParamsOrError = export interface CreateAuthRouteHandlers { onOAuthCallback( - params: ParamsOrError<{ tokenData: TokenData; isSignUp: boolean }> + params: ParamsOrError<{ + tokenData: TokenData; + provider: BuiltinOAuthProviderNames; + isSignUp: boolean; + }> ): void; onEmailPasswordSignIn(params: ParamsOrError<{ tokenData: TokenData }>): void; onEmailPasswordSignUp( @@ -43,7 +47,15 @@ export interface CreateAuthRouteHandlers { > ): void; onBuiltinUICallback( - params: ParamsOrError<{ tokenData: TokenData | null; isSignUp: boolean }> + params: ParamsOrError< + ( + | { + tokenData: TokenData; + provider: BuiltinProviderNames; + } + | { tokenData: null; provider: null } + ) & { isSignUp: boolean } + > ): void; onSignout(): void; } @@ -156,7 +168,14 @@ export class NextAppAuth extends NextAuth { }); cookies().delete(this.options.pkceVerifierCookieName); - return onOAuthCallback({ error: null, tokenData, isSignUp }); + return onOAuthCallback({ + error: null, + tokenData, + provider: req.nextUrl.searchParams.get( + "provider" + ) as BuiltinOAuthProviderNames, + isSignUp, + }); } case "emailpassword/verify": { if (!onEmailVerify) { @@ -224,6 +243,7 @@ export class NextAppAuth extends NextAuth { return onBuiltinUICallback({ error: null, tokenData: null, + provider: null, isSignUp: true, }); } @@ -257,7 +277,14 @@ export class NextAppAuth extends NextAuth { }); cookies().delete(this.options.pkceVerifierCookieName); - return onBuiltinUICallback({ error: null, tokenData, isSignUp }); + return onBuiltinUICallback({ + error: null, + tokenData, + provider: req.nextUrl.searchParams.get( + "provider" + ) as BuiltinProviderNames, + isSignUp, + }); } case "builtin/signin": case "builtin/signup": { From 4984e5e79a16ab6e5a3ab6fb2e45a0e63d248347 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Tue, 21 Nov 2023 14:18:57 +0000 Subject: [PATCH 8/9] Use generic param instead of cast Co-authored-by: Scott Trinh --- packages/auth-nextjs/src/shared.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/auth-nextjs/src/shared.ts b/packages/auth-nextjs/src/shared.ts index 8563dd33b..545c733b6 100644 --- a/packages/auth-nextjs/src/shared.ts +++ b/packages/auth-nextjs/src/shared.ts @@ -75,9 +75,9 @@ export class NextAuthSession { async isLoggedIn() { if (!this.authToken) return false; try { - return (await this.client.querySingle( + return await this.client.querySingle( `select exists global ext::auth::ClientTokenIdentity` - )) as boolean; + ); } catch { return false; } From 0f81a436577f3c92ee4938a984a4e28e915b40bc Mon Sep 17 00:00:00 2001 From: James Clarke Date: Tue, 21 Nov 2023 14:43:02 +0000 Subject: [PATCH 9/9] Fix missing `await` --- packages/auth-core/src/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/auth-core/src/core.ts b/packages/auth-core/src/core.ts index 0d6fb3862..b2efeb86a 100644 --- a/packages/auth-core/src/core.ts +++ b/packages/auth-core/src/core.ts @@ -146,7 +146,7 @@ export class Auth { } async sendPasswordResetEmail(email: string, resetUrl: string) { - const { challenge, verifier } = pkce.createVerifierChallengePair(); + const { challenge, verifier } = await pkce.createVerifierChallengePair(); return { verifier, ...(await this._post<{ email_sent: string }>("send-reset-email", {