mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
fix: Ensure OTP is bound to workspace (#12096)
* fix: Ensure OTP is bound to teamId * fix: Address review feedback on OTP tenant scoping - Trim whitespace in VerificationCode Redis keys to match DB lookup normalization. - Redirect with invalid-code (rather than leaking a backend error) when no user exists for the email in the resolved team. - Correct retrieve() JSDoc to state undefined instead of null. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import { RateLimiterStrategy } from "@server/utils/RateLimiter";
|
||||
import { VerificationCode } from "@server/utils/VerificationCode";
|
||||
import { signIn } from "@server/utils/authentication";
|
||||
import { getUserForEmailSigninToken } from "@server/utils/jwt";
|
||||
import { getTeamFromContext } from "@server/utils/passport";
|
||||
import * as T from "./schema";
|
||||
import { CSRF } from "@shared/constants";
|
||||
|
||||
@@ -128,28 +129,33 @@ const emailCallback = async (ctx: APIContext<T.EmailCallbackReq>) => {
|
||||
return ctx.redirectOnClient(url.toString(), "POST");
|
||||
}
|
||||
|
||||
let user!: User;
|
||||
let user: User | null = null;
|
||||
|
||||
try {
|
||||
if (token) {
|
||||
user = await getUserForEmailSigninToken(ctx, token as string);
|
||||
} else if (code && email) {
|
||||
const team = await getTeamFromContext(ctx);
|
||||
|
||||
if (!team) {
|
||||
ctx.redirect("/?notice=auth-error&description=Unknown%20team");
|
||||
return;
|
||||
}
|
||||
|
||||
user = await User.scope("withTeam").findOne({
|
||||
rejectOnEmpty: true,
|
||||
where: {
|
||||
teamId: team.id,
|
||||
email: email.trim().toLowerCase(),
|
||||
},
|
||||
});
|
||||
|
||||
const isValid = await VerificationCode.verify(email, code);
|
||||
|
||||
if (!isValid) {
|
||||
if (!user || !(await VerificationCode.verify(team.id, email, code))) {
|
||||
ctx.redirect(`/?notice=invalid-code`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the code after successful verification
|
||||
await VerificationCode.delete(email);
|
||||
await VerificationCode.delete(team.id, email);
|
||||
} else {
|
||||
ctx.redirect("/?notice=auth-error&description=Missing%20token");
|
||||
return;
|
||||
@@ -159,6 +165,10 @@ const emailCallback = async (ctx: APIContext<T.EmailCallbackReq>) => {
|
||||
return ctx.redirect(`/?notice=auth-error&description=${err.message}`);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return ctx.redirect(`/?notice=invalid-code`);
|
||||
}
|
||||
|
||||
if (!user.team.emailSigninEnabled) {
|
||||
return ctx.redirect(
|
||||
"/?notice=auth-error&description=Disabled%20signin%20method"
|
||||
|
||||
Reference in New Issue
Block a user