Skip to content

Commit

Permalink
Set verifier in cookie for email-only verification
Browse files Browse the repository at this point in the history
  • Loading branch information
scotttrinh committed Mar 14, 2024
1 parent 0e57f6a commit ecaee24
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 68 deletions.
58 changes: 40 additions & 18 deletions packages/auth-express/src/index.ts
Expand Up @@ -516,24 +516,46 @@ export class ExpressAuth {
next(err);
}
},
resendVerificationEmail: async (
req: AuthRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
const [verificationToken] = _extractParams(
req.body,
["verification_token"],
"verification_token missing from request body"
);
(await this.core).resendVerificationEmail(verificationToken);
res.status(204);
next();
} catch (err) {
next(err);
}
},
resendVerificationEmail:
(verifyUrl?: string) =>
async (req: AuthRequest, res: ExpressResponse, next: NextFunction) => {
try {
if ("verification_token" in req.body) {
const verificationToken = req.body.verification_token;
if (typeof verificationToken !== "string") {
throw new InvalidDataError(
"expected 'verification_token' to be a string"
);
}
await (await this.core).resendVerificationEmail(verificationToken);
} else if ("email" in req.body) {
const email = req.body.email;
if (typeof email !== "string") {
throw new InvalidDataError("expected 'email' to be a string");
}
if (!verifyUrl) {
throw new InvalidDataError(
"verifyUrl is required when email is provided"
);
}
const { verifier } = await (
await this.core
).resendVerificationEmailForEmail(email, verifyUrl);
res.cookie(this.options.pkceVerifierCookieName, verifier, {
httpOnly: true,
sameSite: "strict",
});
} else {
throw new InvalidDataError(
"verification_token or email missing from request body"
);
}
res.status(204);
next();
} catch (err) {
next(err);
}
},
};
}

Expand Down
52 changes: 28 additions & 24 deletions packages/auth-nextjs/src/app/index.ts
Expand Up @@ -145,37 +145,41 @@ export class NextAppAuth extends NextAuth {
emailPasswordResendVerificationEmail: async (
data: FormData | { verification_token: string } | { email: string }
) => {
let email;
let verificationToken;
try {
[verificationToken] = _extractParams(
data,
["verification_token"],
"verification_token missing"
);
} catch (tokenError) {
try {
[email] = _extractParams(data, ["email"], "email missing");
} catch (emailError) {
const bothParamsMissing = [tokenError, emailError]
.map((err) => (err as Error).message)
.join(" and ");

throw new InvalidDataError(
`${bothParamsMissing}. Either one is required.`
);
}
}
const verificationToken =
data instanceof FormData
? data.get("verification_token")
: "verification_token" in data
? data.verification_token
: null;
const email =
data instanceof FormData
? data.get("email")
: "email" in data
? data.email
: null;

if (verificationToken) {
await (await this.core).resendVerificationEmail(verificationToken);
} else if (email) {
await (
await this.core
).resendVerificationEmail(verificationToken.toString());
} else if (email) {
const { verifier } = await (
await this.core
).resendVerificationEmailForEmail(
email,
email.toString(),
`${this._authRoute}/emailpassword/verify`
);

cookies().set({
name: this.options.pkceVerifierCookieName,
value: verifier,
httpOnly: true,
sameSite: "strict",
});
} else {
throw new InvalidDataError(
"either verification_token or email must be provided"
);
}
},
};
Expand Down
5 changes: 5 additions & 0 deletions packages/auth-nextjs/src/pages/client.ts
Expand Up @@ -54,6 +54,11 @@ export class NextPagesClientAuth extends NextAuthHelpers {
| {
verification_token: string;
}
| {
email: string;
verify_url: string;
challenge: string;
}
| FormData
) {
return await apiRequest(
Expand Down
51 changes: 41 additions & 10 deletions packages/auth-nextjs/src/shared.ts
Expand Up @@ -506,15 +506,44 @@ export abstract class NextAuth extends NextAuthHelpers {
case "emailpassword/resend-verification-email": {
const data = await _getReqBody(req);
const isAction = _isAction(data);
const [verificationToken] = _extractParams(
data,
["verification_token"],
"verification_token missing from request body"
);
(await this.core).resendVerificationEmail(verificationToken);
return isAction
? Response.json({ _data: null })
: new Response(null, { status: 204 });
const verificationToken =
data instanceof FormData
? data.get("verification_token")?.toString()
: data.verification_token;
const email =
data instanceof FormData
? data.get("email")?.toString()
: data.email;

if (verificationToken) {
await (
await this.core
).resendVerificationEmail(verificationToken.toString());
return isAction
? Response.json({ _data: null })
: new Response(null, { status: 204 });
} else if (email) {
const { verifier } = await (
await this.core
).resendVerificationEmailForEmail(
email.toString(),
`${this._authRoute}/emailpassword/verify`
);
cookies().set({
name: this.options.pkceVerifierCookieName,
value: verifier,
httpOnly: true,
sameSite: "strict",
path: "/",
});
return isAction
? Response.json({ _data: null })
: new Response(null, { status: 204 });
} else {
throw new InvalidDataError(
"verification_token or email missing from request body"
);
}
}
default:
return new Response("Unknown auth route", {
Expand Down Expand Up @@ -550,7 +579,9 @@ export class NextAuthSession {
}
}

function _getReqBody(req: NextRequest) {
function _getReqBody(
req: NextRequest
): Promise<FormData | Record<string, unknown>> {
return req.headers.get("Content-Type") === "application/json"
? req.json()
: req.formData();
Expand Down
43 changes: 34 additions & 9 deletions packages/auth-remix/src/server.ts
Expand Up @@ -496,21 +496,22 @@ export class RemixServerAuth extends RemixClientAuth {

async emailPasswordResendVerificationEmail(
req: Request,
data?: { verification_token: string }
data?: { verification_token: string } | { email: string }
): Promise<{ headers: Headers }>;
async emailPasswordResendVerificationEmail<Res>(
req: Request,
cb: (error?: Error) => Res | Promise<Res>
): Promise<Res extends Response ? Res : TypedResponse<Res>>;
async emailPasswordResendVerificationEmail<Res>(
req: Request,
data: { verification_token: string },
data: { verification_token: string } | { email: string },
cb: (error?: Error) => Res | Promise<Res>
): Promise<Res extends Response ? Res : TypedResponse<Res>>;
async emailPasswordResendVerificationEmail<Res>(
req: Request,
dataOrCb?:
| { verification_token: string }
| { email: string }
| ((error?: Error) => Res | Promise<Res>),
cb?: (error?: Error) => Res | Promise<Res>
): Promise<
Expand All @@ -520,14 +521,38 @@ export class RemixServerAuth extends RemixClientAuth {
| (Res extends Response ? Res : TypedResponse<Res>)
> {
return handleAction(
async (data) => {
const [verificationToken] = _extractParams(
data,
["verification_token"],
"verification_token missing"
);
async (data, headers) => {
const verificationToken =
data instanceof FormData
? data.get("verification_token")
: data.verification_token;
const email = data instanceof FormData ? data.get("email") : data.email;

if (verificationToken) {
await (
await this.core
).resendVerificationEmail(verificationToken.toString());
} else if (email) {
const { verifier } = await (
await this.core
).resendVerificationEmailForEmail(
email.toString(),
`${this._authRoute}/emailpassword/verify`
);

await (await this.core).resendVerificationEmail(verificationToken);
headers.append(
"Set-Cookie",
cookie.serialize(this.options.pkceVerifierCookieName, verifier, {
httpOnly: true,
sameSite: "strict",
path: "/",
})
);
} else {
throw new InvalidDataError(
"verification_token or email missing. Either one is required."
);
}
},
req,
dataOrCb,
Expand Down
42 changes: 35 additions & 7 deletions packages/auth-sveltekit/src/server.ts
Expand Up @@ -172,15 +172,43 @@ export class ServerRequestAuth extends ClientAuth {
}

async emailPasswordResendVerificationEmail(
data: { verification_token: string } | FormData
data: { verification_token: string } | { email: string } | FormData
): Promise<void> {
const [verificationToken] = extractParams(
data,
["verification_token"],
"verification_token missing"
);
const verificationToken =
data instanceof FormData
? data.get("verification_token")
: "verification_token" in data
? data.verification_token
: null;
const email =
data instanceof FormData
? data.get("email")
: "email" in data
? data.email
: null;

if (verificationToken) {
return await (
await this.core
).resendVerificationEmail(verificationToken.toString());
} else if (email) {
const { verifier } = await (
await this.core
).resendVerificationEmailForEmail(
email.toString(),
`${this.config.authRoute}/emailpassword/verify`
);

await (await this.core).resendVerificationEmail(verificationToken);
this.cookies.set(this.config.pkceVerifierCookieName, verifier, {
httpOnly: true,
sameSite: "strict",
path: "/",
});
} else {
throw new InvalidDataError(
"expected 'verification_token' or 'email' in data"
);
}
}

async emailPasswordSignIn(
Expand Down

0 comments on commit ecaee24

Please sign in to comment.