Skip to content

Commit fd628bb

Browse files
authored
Multiple fixes/improvements to auth-nextjs (#785)
1 parent b2c2101 commit fd628bb

File tree

3 files changed

+109
-40
lines changed

3 files changed

+109
-40
lines changed

packages/auth-core/src/core.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class Auth {
106106
password: string,
107107
verifyUrl: string
108108
): Promise<
109-
| { status: "complete"; tokenData: TokenData }
109+
| { status: "complete"; verifier: string; tokenData: TokenData }
110110
| { status: "verificationRequired"; verifier: string }
111111
> {
112112
const { challenge, verifier } = await pkce.createVerifierChallengePair();
@@ -122,6 +122,7 @@ export class Auth {
122122
if ("code" in result) {
123123
return {
124124
status: "complete",
125+
verifier,
125126
tokenData: await this.getToken(result.code, verifier),
126127
};
127128
} else {
@@ -145,7 +146,7 @@ export class Auth {
145146
}
146147

147148
async sendPasswordResetEmail(email: string, resetUrl: string) {
148-
const { challenge, verifier } = pkce.createVerifierChallengePair();
149+
const { challenge, verifier } = await pkce.createVerifierChallengePair();
149150
return {
150151
verifier,
151152
...(await this._post<{ email_sent: string }>("send-reset-email", {

packages/auth-nextjs/src/app/index.ts

Lines changed: 87 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,53 @@ import { cookies } from "next/headers";
99
import { redirect } from "next/navigation";
1010
import type { NextRequest } from "next/server";
1111

12-
import { NextAuth, NextAuthSession, type NextAuthOptions } from "../shared";
12+
import {
13+
NextAuth,
14+
NextAuthSession,
15+
type NextAuthOptions,
16+
BuiltinProviderNames,
17+
} from "../shared";
1318

14-
export { NextAuthSession, type NextAuthOptions };
19+
export {
20+
NextAuthSession,
21+
type NextAuthOptions,
22+
type BuiltinProviderNames,
23+
type TokenData,
24+
};
1525

16-
type ParamsOrError<Result extends object> =
17-
| ({ error: null } & Result)
18-
| ({ error: Error } & { [Key in keyof Result]?: undefined });
26+
type ParamsOrError<Result extends object, ErrorDetails extends object = {}> =
27+
| ({ error: null } & { [Key in keyof ErrorDetails]?: undefined } & Result)
28+
| ({ error: Error } & ErrorDetails & { [Key in keyof Result]?: undefined });
1929

2030
export interface CreateAuthRouteHandlers {
2131
onOAuthCallback(
22-
params: ParamsOrError<{ tokenData: TokenData; isSignUp: boolean }>
32+
params: ParamsOrError<{
33+
tokenData: TokenData;
34+
provider: BuiltinOAuthProviderNames;
35+
isSignUp: boolean;
36+
}>
2337
): void;
2438
onEmailPasswordSignIn(params: ParamsOrError<{ tokenData: TokenData }>): void;
2539
onEmailPasswordSignUp(
2640
params: ParamsOrError<{ tokenData: TokenData | null }>
2741
): void;
2842
onEmailPasswordReset(params: ParamsOrError<{ tokenData: TokenData }>): void;
29-
onEmailVerify(params: ParamsOrError<{ tokenData: TokenData }>): void;
43+
onEmailVerify(
44+
params: ParamsOrError<
45+
{ tokenData: TokenData },
46+
{ verificationToken?: string }
47+
>
48+
): void;
3049
onBuiltinUICallback(
31-
params: ParamsOrError<{ tokenData: TokenData | null; isSignUp: boolean }>
50+
params: ParamsOrError<
51+
(
52+
| {
53+
tokenData: TokenData;
54+
provider: BuiltinProviderNames;
55+
}
56+
| { tokenData: null; provider: null }
57+
) & { isSignUp: boolean }
58+
>
3259
): void;
3360
onSignout(): void;
3461
}
@@ -48,6 +75,10 @@ export class NextAppAuth extends NextAuth {
4875
);
4976
}
5077

78+
async getProvidersInfo() {
79+
return (await this.core).getProvidersInfo();
80+
}
81+
5182
createAuthRouteHandlers({
5283
onOAuthCallback,
5384
onEmailPasswordSignIn,
@@ -137,7 +168,14 @@ export class NextAppAuth extends NextAuth {
137168
});
138169
cookies().delete(this.options.pkceVerifierCookieName);
139170

140-
return onOAuthCallback({ error: null, tokenData, isSignUp });
171+
return onOAuthCallback({
172+
error: null,
173+
tokenData,
174+
provider: req.nextUrl.searchParams.get(
175+
"provider"
176+
) as BuiltinOAuthProviderNames,
177+
isSignUp,
178+
});
141179
}
142180
case "emailpassword/verify": {
143181
if (!onEmailVerify) {
@@ -158,6 +196,7 @@ export class NextAppAuth extends NextAuth {
158196
if (!verifier) {
159197
return onEmailVerify({
160198
error: new Error("no pkce verifier cookie found"),
199+
verificationToken,
161200
});
162201
}
163202
let tokenData: TokenData;
@@ -168,6 +207,7 @@ export class NextAppAuth extends NextAuth {
168207
} catch (err) {
169208
return onEmailVerify({
170209
error: err instanceof Error ? err : new Error(String(err)),
210+
verificationToken,
171211
});
172212
}
173213
cookies().set({
@@ -203,6 +243,7 @@ export class NextAppAuth extends NextAuth {
203243
return onBuiltinUICallback({
204244
error: null,
205245
tokenData: null,
246+
provider: null,
206247
isSignUp: true,
207248
});
208249
}
@@ -236,7 +277,14 @@ export class NextAppAuth extends NextAuth {
236277
});
237278
cookies().delete(this.options.pkceVerifierCookieName);
238279

239-
return onBuiltinUICallback({ error: null, tokenData, isSignUp });
280+
return onBuiltinUICallback({
281+
error: null,
282+
tokenData,
283+
provider: req.nextUrl.searchParams.get(
284+
"provider"
285+
) as BuiltinProviderNames,
286+
isSignUp,
287+
});
240288
}
241289
case "builtin/signin":
242290
case "builtin/signup": {
@@ -328,6 +376,12 @@ export class NextAppAuth extends NextAuth {
328376
error: err instanceof Error ? err : new Error(String(err)),
329377
});
330378
}
379+
cookies().set({
380+
name: this.options.pkceVerifierCookieName,
381+
value: result.verifier,
382+
httpOnly: true,
383+
sameSite: "strict",
384+
});
331385
if (result.status === "complete") {
332386
cookies().set({
333387
name: this.options.authCookieName,
@@ -340,18 +394,12 @@ export class NextAppAuth extends NextAuth {
340394
tokenData: result.tokenData,
341395
});
342396
} else {
343-
cookies().set({
344-
name: this.options.pkceVerifierCookieName,
345-
value: result.verifier,
346-
httpOnly: true,
347-
sameSite: "strict",
348-
});
349397
return onEmailPasswordSignUp({ error: null, tokenData: null });
350398
}
351399
}
352400
case "emailpassword/send-reset-email": {
353-
if (!this.options.passwordResetUrl) {
354-
throw new Error(`'passwordResetUrl' option not configured`);
401+
if (!this.options.passwordResetPath) {
402+
throw new Error(`'passwordResetPath' option not configured`);
355403
}
356404
const [email] = _extractParams(
357405
await _getReqBody(req),
@@ -360,7 +408,13 @@ export class NextAppAuth extends NextAuth {
360408
);
361409
const { verifier } = await (
362410
await this.core
363-
).sendPasswordResetEmail(email, this.options.passwordResetUrl);
411+
).sendPasswordResetEmail(
412+
email,
413+
new URL(
414+
this.options.passwordResetPath,
415+
this.options.baseUrl
416+
).toString()
417+
);
364418
cookies().set({
365419
name: this.options.pkceVerifierCookieName,
366420
value: verifier,
@@ -465,6 +519,12 @@ export class NextAppAuth extends NextAuth {
465519
password,
466520
`${this._authRoute}/emailpassword/verify`
467521
);
522+
cookies().set({
523+
name: this.options.pkceVerifierCookieName,
524+
value: result.verifier,
525+
httpOnly: true,
526+
sameSite: "strict",
527+
});
468528
if (result.status === "complete") {
469529
cookies().set({
470530
name: this.options.authCookieName,
@@ -473,28 +533,24 @@ export class NextAppAuth extends NextAuth {
473533
sameSite: "strict",
474534
});
475535
return result.tokenData;
476-
} else {
477-
cookies().set({
478-
name: this.options.pkceVerifierCookieName,
479-
value: result.verifier,
480-
httpOnly: true,
481-
sameSite: "strict",
482-
});
483-
return null;
484536
}
537+
return null;
485538
},
486539
emailPasswordSendPasswordResetEmail: async (
487540
data: FormData | { email: string }
488541
) => {
489-
if (!this.options.passwordResetUrl) {
490-
throw new Error(`'passwordResetUrl' option not configured`);
542+
if (!this.options.passwordResetPath) {
543+
throw new Error(`'passwordResetPath' option not configured`);
491544
}
492545
const [email] = _extractParams(data, ["email"], "email missing");
493546
const { verifier } = await (
494547
await this.core
495548
).sendPasswordResetEmail(
496549
email,
497-
`${this.options.baseUrl}/${this.options.passwordResetUrl}`
550+
new URL(
551+
this.options.passwordResetPath,
552+
this.options.baseUrl
553+
).toString()
498554
);
499555
cookies().set({
500556
name: this.options.pkceVerifierCookieName,
@@ -504,7 +560,7 @@ export class NextAppAuth extends NextAuth {
504560
});
505561
},
506562
emailPasswordResetPassword: async (
507-
data: FormData | { resetToken: string; password: string }
563+
data: FormData | { reset_token: string; password: string }
508564
) => {
509565
const verifier = cookies().get(
510566
this.options.pkceVerifierCookieName

packages/auth-nextjs/src/shared.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import { Client } from "edgedb";
2-
import { Auth, BuiltinOAuthProviderNames } from "@edgedb/auth-core";
2+
import {
3+
Auth,
4+
BuiltinOAuthProviderNames,
5+
emailPasswordProviderName,
6+
} from "@edgedb/auth-core";
7+
8+
export type BuiltinProviderNames =
9+
| BuiltinOAuthProviderNames
10+
| typeof emailPasswordProviderName;
311

412
export interface NextAuthOptions {
513
baseUrl: string;
614
authRoutesPath?: string;
715
authCookieName?: string;
816
pkceVerifierCookieName?: string;
9-
passwordResetUrl?: string;
17+
passwordResetPath?: string;
1018
}
1119

12-
type OptionalOptions = "passwordResetUrl";
20+
type OptionalOptions = "passwordResetPath";
1321

1422
export abstract class NextAuth {
1523
/** @internal */
@@ -24,7 +32,7 @@ export abstract class NextAuth {
2432
authCookieName: options.authCookieName ?? "edgedb-session",
2533
pkceVerifierCookieName:
2634
options.pkceVerifierCookieName ?? "edgedb-pkce-verifier",
27-
passwordResetUrl: options.passwordResetUrl,
35+
passwordResetPath: options.passwordResetPath,
2836
};
2937
}
3038

@@ -66,8 +74,12 @@ export class NextAuthSession {
6674

6775
async isLoggedIn() {
6876
if (!this.authToken) return false;
69-
return (await this.client.querySingle(
70-
`select exists global ext::auth::ClientTokenIdentity`
71-
)) as boolean;
77+
try {
78+
return await this.client.querySingle<boolean>(
79+
`select exists global ext::auth::ClientTokenIdentity`
80+
);
81+
} catch {
82+
return false;
83+
}
7284
}
7385
}

0 commit comments

Comments
 (0)