Skip to content

Commit

Permalink
Multiple fixes/improvements to auth-nextjs (#785)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaclarke authored Nov 22, 2023
1 parent b2c2101 commit fd628bb
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 40 deletions.
5 changes: 3 additions & 2 deletions packages/auth-core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -122,6 +122,7 @@ export class Auth {
if ("code" in result) {
return {
status: "complete",
verifier,
tokenData: await this.getToken(result.code, verifier),
};
} else {
Expand All @@ -145,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", {
Expand Down
118 changes: 87 additions & 31 deletions packages/auth-nextjs/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,53 @@ import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import type { NextRequest } from "next/server";

import { NextAuth, NextAuthSession, type NextAuthOptions } from "../shared";
import {

Check failure on line 12 in packages/auth-nextjs/src/app/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Import "BuiltinProviderNames" is only used as types

Check failure on line 12 in packages/auth-nextjs/src/app/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Import "BuiltinProviderNames" is only used as types

Check failure on line 12 in packages/auth-nextjs/src/app/index.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Import "BuiltinProviderNames" is only used as types

Check failure on line 12 in packages/auth-nextjs/src/app/index.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Import "BuiltinProviderNames" is only used as types

Check failure on line 12 in packages/auth-nextjs/src/app/index.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Import "BuiltinProviderNames" is only used as types

Check failure on line 12 in packages/auth-nextjs/src/app/index.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Import "BuiltinProviderNames" is only used as types
NextAuth,
NextAuthSession,
type NextAuthOptions,
BuiltinProviderNames,
} from "../shared";

export { NextAuthSession, type NextAuthOptions };
export {
NextAuthSession,
type NextAuthOptions,
type BuiltinProviderNames,
type TokenData,
};

type ParamsOrError<Result extends object> =
| ({ error: null } & Result)
| ({ error: Error } & { [Key in keyof Result]?: undefined });
type ParamsOrError<Result extends object, ErrorDetails extends object = {}> =
| ({ error: null } & { [Key in keyof ErrorDetails]?: undefined } & Result)
| ({ error: Error } & ErrorDetails & { [Key in keyof Result]?: undefined });

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(
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 }>
params: ParamsOrError<
(
| {
tokenData: TokenData;
provider: BuiltinProviderNames;
}
| { tokenData: null; provider: null }
) & { isSignUp: boolean }
>
): void;
onSignout(): void;
}
Expand All @@ -48,6 +75,10 @@ export class NextAppAuth extends NextAuth {
);
}

async getProvidersInfo() {
return (await this.core).getProvidersInfo();
}

createAuthRouteHandlers({
onOAuthCallback,
onEmailPasswordSignIn,
Expand Down Expand Up @@ -137,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) {
Expand All @@ -158,6 +196,7 @@ export class NextAppAuth extends NextAuth {
if (!verifier) {
return onEmailVerify({
error: new Error("no pkce verifier cookie found"),
verificationToken,
});
}
let tokenData: TokenData;
Expand All @@ -168,6 +207,7 @@ export class NextAppAuth extends NextAuth {
} catch (err) {
return onEmailVerify({
error: err instanceof Error ? err : new Error(String(err)),
verificationToken,
});
}
cookies().set({
Expand Down Expand Up @@ -203,6 +243,7 @@ export class NextAppAuth extends NextAuth {
return onBuiltinUICallback({
error: null,
tokenData: null,
provider: null,
isSignUp: true,
});
}
Expand Down Expand Up @@ -236,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": {
Expand Down Expand Up @@ -328,6 +376,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,
Expand All @@ -340,18 +394,12 @@ 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 });
}
}
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),
Expand All @@ -360,7 +408,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,
Expand Down Expand Up @@ -465,6 +519,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,
Expand All @@ -473,28 +533,24 @@ 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 }
) => {
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,
Expand All @@ -504,7 +560,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
Expand Down
26 changes: 19 additions & 7 deletions packages/auth-nextjs/src/shared.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { Client } from "edgedb";

Check failure on line 1 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 1 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 1 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 1 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 1 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

All imports in the declaration are only used as types. Use `import type`

Check failure on line 1 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

All imports in the declaration are only used as types. Use `import type`
import { Auth, BuiltinOAuthProviderNames } from "@edgedb/auth-core";
import {

Check failure on line 2 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 2)

Imports "BuiltinOAuthProviderNames" and "emailPasswordProviderName" are only used as types

Check failure on line 2 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, 3)

Imports "BuiltinOAuthProviderNames" and "emailPasswordProviderName" are only used as types

Check failure on line 2 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (18, ubuntu-latest, stable)

Imports "BuiltinOAuthProviderNames" and "emailPasswordProviderName" are only used as types

Check failure on line 2 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 18, nightly)

Imports "BuiltinOAuthProviderNames" and "emailPasswordProviderName" are only used as types

Check failure on line 2 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (20, ubuntu-latest, stable)

Imports "BuiltinOAuthProviderNames" and "emailPasswordProviderName" are only used as types

Check failure on line 2 in packages/auth-nextjs/src/shared.ts

View workflow job for this annotation

GitHub Actions / test (macos-latest, 18, stable)

Imports "BuiltinOAuthProviderNames" and "emailPasswordProviderName" are only used as types
Auth,
BuiltinOAuthProviderNames,
emailPasswordProviderName,
} from "@edgedb/auth-core";

export type BuiltinProviderNames =
| BuiltinOAuthProviderNames
| typeof emailPasswordProviderName;

export interface NextAuthOptions {
baseUrl: string;
authRoutesPath?: string;
authCookieName?: string;
pkceVerifierCookieName?: string;
passwordResetUrl?: string;
passwordResetPath?: string;
}

type OptionalOptions = "passwordResetUrl";
type OptionalOptions = "passwordResetPath";

export abstract class NextAuth {
/** @internal */
Expand All @@ -24,7 +32,7 @@ export abstract class NextAuth {
authCookieName: options.authCookieName ?? "edgedb-session",
pkceVerifierCookieName:
options.pkceVerifierCookieName ?? "edgedb-pkce-verifier",
passwordResetUrl: options.passwordResetUrl,
passwordResetPath: options.passwordResetPath,
};
}

Expand Down Expand Up @@ -66,8 +74,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<boolean>(
`select exists global ext::auth::ClientTokenIdentity`
);
} catch {
return false;
}
}
}

0 comments on commit fd628bb

Please sign in to comment.