successful log in loop poc

This commit is contained in:
miloschwartz
2025-04-13 17:57:27 -04:00
parent 7556a59e11
commit 53be2739bb
37 changed files with 789 additions and 474 deletions

View File

@@ -16,6 +16,7 @@ import logger from "@server/logger";
import { unauthorized } from "@server/auth/unauthorizedResponse";
import { invalidateAllSessions } from "@server/auth/sessions/app";
import { passwordSchema } from "@server/auth/passwordSchema";
import { UserType } from "@server/types/UserTypes";
export const changePasswordBody = z
.object({
@@ -50,6 +51,15 @@ export async function changePassword(
const { newPassword, oldPassword, code } = parsedBody.data;
const user = req.user as User;
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Two-factor authentication is not supported for external users"
)
);
}
try {
if (newPassword === oldPassword) {
return next(
@@ -62,7 +72,7 @@ export async function changePassword(
const validPassword = await verifyPassword(
oldPassword,
user.passwordHash
user.passwordHash!
);
if (!validPassword) {
return next(unauthorized());

View File

@@ -14,6 +14,7 @@ import { sendEmail } from "@server/emails";
import TwoFactorAuthNotification from "@server/emails/templates/TwoFactorAuthNotification";
import config from "@server/lib/config";
import { unauthorized } from "@server/auth/unauthorizedResponse";
import { UserType } from "@server/types/UserTypes";
export const disable2faBody = z
.object({
@@ -47,8 +48,17 @@ export async function disable2fa(
const { password, code } = parsedBody.data;
const user = req.user as User;
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Two-factor authentication is not supported for external users"
)
);
}
try {
const validPassword = await verifyPassword(password, user.passwordHash);
const validPassword = await verifyPassword(password, user.passwordHash!);
if (!validPassword) {
return next(unauthorized());
}
@@ -99,11 +109,11 @@ export async function disable2fa(
sendEmail(
TwoFactorAuthNotification({
email: user.email,
email: user.email!, // email is not null because we are checking user.type
enabled: false
}),
{
to: user.email,
to: user.email!,
from: config.getRawConfig().email?.no_reply,
subject: "Two-factor authentication disabled"
}

View File

@@ -7,7 +7,7 @@ import db from "@server/db";
import { users } from "@server/db/schemas";
import HttpCode from "@server/types/HttpCode";
import response from "@server/lib/response";
import { eq } from "drizzle-orm";
import { eq, and } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
@@ -17,6 +17,7 @@ import config from "@server/lib/config";
import logger from "@server/logger";
import { verifyPassword } from "@server/auth/password";
import { verifySession } from "@server/auth/sessions/verifySession";
import { UserType } from "@server/types/UserTypes";
export const loginBodySchema = z
.object({
@@ -69,7 +70,9 @@ export async function login(
const existingUserRes = await db
.select()
.from(users)
.where(eq(users.email, email));
.where(
and(eq(users.type, UserType.Internal), eq(users.email, email))
);
if (!existingUserRes || !existingUserRes.length) {
if (config.getRawConfig().app.log_failed_attempts) {
logger.info(
@@ -88,7 +91,7 @@ export async function login(
const validPassword = await verifyPassword(
password,
existingUser.passwordHash
existingUser.passwordHash!
);
if (!validPassword) {
if (config.getRawConfig().app.log_failed_attempts) {

View File

@@ -6,6 +6,7 @@ import { User } from "@server/db/schemas";
import { sendEmailVerificationCode } from "../../auth/sendEmailVerificationCode";
import config from "@server/lib/config";
import logger from "@server/logger";
import { UserType } from "@server/types/UserTypes";
export type RequestEmailVerificationCodeResponse = {
codeSent: boolean;
@@ -28,6 +29,15 @@ export async function requestEmailVerificationCode(
try {
const user = req.user as User;
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Email verification is not supported for external users"
)
);
}
if (user.emailVerified) {
return next(
createHttpError(
@@ -37,7 +47,7 @@ export async function requestEmailVerificationCode(
);
}
await sendEmailVerificationCode(user.email, user.userId);
await sendEmailVerificationCode(user.email!, user.userId);
return response<RequestEmailVerificationCodeResponse>(res, {
data: {

View File

@@ -74,7 +74,7 @@ export async function requestPasswordReset(
await trx.insert(passwordResetTokens).values({
userId: existingUser[0].userId,
email: existingUser[0].email,
email: existingUser[0].email!,
tokenHash,
expiresAt: createDate(new TimeSpan(2, "h")).getTime()
});

View File

@@ -12,6 +12,7 @@ import { createTOTPKeyURI } from "oslo/otp";
import logger from "@server/logger";
import { verifyPassword } from "@server/auth/password";
import { unauthorized } from "@server/auth/unauthorizedResponse";
import { UserType } from "@server/types/UserTypes";
export const requestTotpSecretBody = z
.object({
@@ -46,8 +47,17 @@ export async function requestTotpSecret(
const user = req.user as User;
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Two-factor authentication is not supported for external users"
)
);
}
try {
const validPassword = await verifyPassword(password, user.passwordHash);
const validPassword = await verifyPassword(password, user.passwordHash!);
if (!validPassword) {
return next(unauthorized());
}
@@ -63,7 +73,7 @@ export async function requestTotpSecret(
const hex = crypto.getRandomValues(new Uint8Array(20));
const secret = encodeHex(hex);
const uri = createTOTPKeyURI("Pangolin", user.email, hex);
const uri = createTOTPKeyURI("Pangolin", user.email!, hex);
await db
.update(users)

View File

@@ -8,7 +8,7 @@ import createHttpError from "http-errors";
import response from "@server/lib/response";
import { SqliteError } from "better-sqlite3";
import { sendEmailVerificationCode } from "../../auth/sendEmailVerificationCode";
import { eq } from "drizzle-orm";
import { eq, and } from "drizzle-orm";
import moment from "moment";
import {
createSession,
@@ -21,6 +21,7 @@ import logger from "@server/logger";
import { hashPassword } from "@server/auth/password";
import { checkValidInvite } from "@server/auth/checkValidInvite";
import { passwordSchema } from "@server/auth/passwordSchema";
import { UserType } from "@server/types/UserTypes";
export const signupBodySchema = z.object({
email: z
@@ -110,7 +111,9 @@ export async function signup(
const existing = await db
.select()
.from(users)
.where(eq(users.email, email));
.where(
and(eq(users.email, email), eq(users.type, UserType.Internal))
);
if (existing && existing.length > 0) {
if (!config.getRawConfig().flags?.require_email_verification) {
@@ -157,6 +160,8 @@ export async function signup(
await db.insert(users).values({
userId: userId,
type: UserType.Internal,
username: email,
email: email,
passwordHash,
dateCreated: moment().toISOString()

View File

@@ -14,6 +14,7 @@ import logger from "@server/logger";
import { sendEmail } from "@server/emails";
import TwoFactorAuthNotification from "@server/emails/templates/TwoFactorAuthNotification";
import config from "@server/lib/config";
import { UserType } from "@server/types/UserTypes";
export const verifyTotpBody = z
.object({
@@ -48,6 +49,15 @@ export async function verifyTotp(
const user = req.user as User;
if (user.type !== UserType.Internal) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Two-factor authentication is not supported for external users"
)
);
}
if (user.twoFactorEnabled) {
return next(
createHttpError(
@@ -111,11 +121,11 @@ export async function verifyTotp(
sendEmail(
TwoFactorAuthNotification({
email: user.email,
email: user.email!,
enabled: true
}),
{
to: user.email,
to: user.email!,
from: config.getRawConfig().email?.no_reply,
subject: "Two-factor authentication enabled"
}