mirror of
				https://github.com/kyantech/Palmr.git
				synced 2025-11-03 21:43:20 +00:00 
			
		
		
		
	Feat: Implement disable password authentication (#168)
This commit is contained in:
		@@ -147,6 +147,12 @@ const defaultConfigs = [
 | 
				
			|||||||
    type: "boolean",
 | 
					    type: "boolean",
 | 
				
			||||||
    group: "auth-providers",
 | 
					    group: "auth-providers",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    key: "passwordAuthEnabled",
 | 
				
			||||||
 | 
					    value: "true",
 | 
				
			||||||
 | 
					    type: "boolean",
 | 
				
			||||||
 | 
					    group: "security",
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: "serverUrl",
 | 
					    key: "serverUrl",
 | 
				
			||||||
    value: "http://localhost:3333",
 | 
					    value: "http://localhost:3333",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,17 @@ export class AppService {
 | 
				
			|||||||
      throw new Error("JWT Secret cannot be updated through this endpoint");
 | 
					      throw new Error("JWT Secret cannot be updated through this endpoint");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (key === "passwordAuthEnabled") {
 | 
				
			||||||
 | 
					      if (value === "false") {
 | 
				
			||||||
 | 
					        const canDisable = await this.configService.validatePasswordAuthDisable();
 | 
				
			||||||
 | 
					        if (!canDisable) {
 | 
				
			||||||
 | 
					          throw new Error(
 | 
				
			||||||
 | 
					            "Password authentication cannot be disabled. At least one authentication provider must be active."
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const config = await prisma.appConfig.findUnique({
 | 
					    const config = await prisma.appConfig.findUnique({
 | 
				
			||||||
      where: { key },
 | 
					      where: { key },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -64,6 +75,15 @@ export class AppService {
 | 
				
			|||||||
    if (updates.some((update) => update.key === "jwtSecret")) {
 | 
					    if (updates.some((update) => update.key === "jwtSecret")) {
 | 
				
			||||||
      throw new Error("JWT Secret cannot be updated through this endpoint");
 | 
					      throw new Error("JWT Secret cannot be updated through this endpoint");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    const passwordAuthUpdate = updates.find((update) => update.key === "passwordAuthEnabled");
 | 
				
			||||||
 | 
					    if (passwordAuthUpdate && passwordAuthUpdate.value === "false") {
 | 
				
			||||||
 | 
					      const canDisable = await this.configService.validatePasswordAuthDisable();
 | 
				
			||||||
 | 
					      if (!canDisable) {
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					          "Password authentication cannot be disabled. At least one authentication provider must be active."
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const keys = updates.map((update) => update.key);
 | 
					    const keys = updates.map((update) => update.key);
 | 
				
			||||||
    const existingConfigs = await prisma.appConfig.findMany({
 | 
					    const existingConfigs = await prisma.appConfig.findMany({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import { FastifyReply, FastifyRequest } from "fastify";
 | 
					import { FastifyReply, FastifyRequest } from "fastify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ConfigService } from "../config/service";
 | 
				
			||||||
import { UpdateAuthProviderSchema } from "./dto";
 | 
					import { UpdateAuthProviderSchema } from "./dto";
 | 
				
			||||||
import { AuthProvidersService } from "./service";
 | 
					import { AuthProvidersService } from "./service";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@@ -39,9 +40,11 @@ const ERROR_MESSAGES = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export class AuthProvidersController {
 | 
					export class AuthProvidersController {
 | 
				
			||||||
  private authProvidersService: AuthProvidersService;
 | 
					  private authProvidersService: AuthProvidersService;
 | 
				
			||||||
 | 
					  private configService: ConfigService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    this.authProvidersService = new AuthProvidersService();
 | 
					    this.authProvidersService = new AuthProvidersService();
 | 
				
			||||||
 | 
					    this.configService = new ConfigService();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private buildRequestContext(request: FastifyRequest): RequestContext {
 | 
					  private buildRequestContext(request: FastifyRequest): RequestContext {
 | 
				
			||||||
@@ -223,13 +226,24 @@ export class AuthProvidersController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const { id } = request.params;
 | 
					      const { id } = request.params;
 | 
				
			||||||
      const data = request.body;
 | 
					      const data = request.body as any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const existingProvider = await this.authProvidersService.getProviderById(id);
 | 
					      const existingProvider = await this.authProvidersService.getProviderById(id);
 | 
				
			||||||
      if (!existingProvider) {
 | 
					      if (!existingProvider) {
 | 
				
			||||||
        return this.sendErrorResponse(reply, 404, ERROR_MESSAGES.PROVIDER_NOT_FOUND);
 | 
					        return this.sendErrorResponse(reply, 404, ERROR_MESSAGES.PROVIDER_NOT_FOUND);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (data.enabled === false && existingProvider.enabled === true) {
 | 
				
			||||||
 | 
					        const canDisable = await this.configService.validateAllProvidersDisable();
 | 
				
			||||||
 | 
					        if (!canDisable) {
 | 
				
			||||||
 | 
					          return this.sendErrorResponse(
 | 
				
			||||||
 | 
					            reply,
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					            "Cannot disable the last authentication provider when password authentication is disabled"
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const isOfficial = this.authProvidersService.isOfficialProvider(existingProvider.name);
 | 
					      const isOfficial = this.authProvidersService.isOfficialProvider(existingProvider.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (isOfficial) {
 | 
					      if (isOfficial) {
 | 
				
			||||||
@@ -300,6 +314,17 @@ export class AuthProvidersController {
 | 
				
			|||||||
        return this.sendErrorResponse(reply, 400, ERROR_MESSAGES.OFFICIAL_CANNOT_DELETE);
 | 
					        return this.sendErrorResponse(reply, 400, ERROR_MESSAGES.OFFICIAL_CANNOT_DELETE);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (provider.enabled) {
 | 
				
			||||||
 | 
					        const canDisable = await this.configService.validateAllProvidersDisable();
 | 
				
			||||||
 | 
					        if (!canDisable) {
 | 
				
			||||||
 | 
					          return this.sendErrorResponse(
 | 
				
			||||||
 | 
					            reply,
 | 
				
			||||||
 | 
					            400,
 | 
				
			||||||
 | 
					            "Cannot delete the last authentication provider when password authentication is disabled"
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await this.authProvidersService.deleteProvider(id);
 | 
					      await this.authProvidersService.deleteProvider(id);
 | 
				
			||||||
      return this.sendSuccessResponse(reply, undefined, "Provider deleted successfully");
 | 
					      return this.sendSuccessResponse(reply, undefined, "Provider deleted successfully");
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { FastifyReply, FastifyRequest } from "fastify";
 | 
					import { FastifyReply, FastifyRequest } from "fastify";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { env } from "../../env";
 | 
					import { env } from "../../env";
 | 
				
			||||||
 | 
					import { ConfigService } from "../config/service";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  CompleteTwoFactorLoginSchema,
 | 
					  CompleteTwoFactorLoginSchema,
 | 
				
			||||||
  createResetPasswordSchema,
 | 
					  createResetPasswordSchema,
 | 
				
			||||||
@@ -11,6 +12,7 @@ import { AuthService } from "./service";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export class AuthController {
 | 
					export class AuthController {
 | 
				
			||||||
  private authService = new AuthService();
 | 
					  private authService = new AuthService();
 | 
				
			||||||
 | 
					  private configService = new ConfigService();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private getClientInfo(request: FastifyRequest) {
 | 
					  private getClientInfo(request: FastifyRequest) {
 | 
				
			||||||
    const realIP = request.headers["x-real-ip"] as string;
 | 
					    const realIP = request.headers["x-real-ip"] as string;
 | 
				
			||||||
@@ -169,4 +171,15 @@ export class AuthController {
 | 
				
			|||||||
      return reply.status(400).send({ error: error.message });
 | 
					      return reply.status(400).send({ error: error.message });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async getAuthConfig(request: FastifyRequest, reply: FastifyReply) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const passwordAuthEnabled = await this.configService.getValue("passwordAuthEnabled");
 | 
				
			||||||
 | 
					      return reply.send({
 | 
				
			||||||
 | 
					        passwordAuthEnabled: passwordAuthEnabled === "true",
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      return reply.status(400).send({ error: error.message });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -280,4 +280,23 @@ export async function authRoutes(app: FastifyInstance) {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    authController.removeAllTrustedDevices.bind(authController)
 | 
					    authController.removeAllTrustedDevices.bind(authController)
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  app.get(
 | 
				
			||||||
 | 
					    "/auth/config",
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      schema: {
 | 
				
			||||||
 | 
					        tags: ["Authentication"],
 | 
				
			||||||
 | 
					        operationId: "getAuthConfig",
 | 
				
			||||||
 | 
					        summary: "Get Authentication Configuration",
 | 
				
			||||||
 | 
					        description: "Get authentication configuration settings",
 | 
				
			||||||
 | 
					        response: {
 | 
				
			||||||
 | 
					          200: z.object({
 | 
				
			||||||
 | 
					            passwordAuthEnabled: z.boolean().describe("Whether password authentication is enabled"),
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					          400: z.object({ error: z.string().describe("Error message") }),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    authController.getAuthConfig.bind(authController)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,11 @@ export class AuthService {
 | 
				
			|||||||
  private trustedDeviceService = new TrustedDeviceService();
 | 
					  private trustedDeviceService = new TrustedDeviceService();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async login(data: LoginInput, userAgent?: string, ipAddress?: string) {
 | 
					  async login(data: LoginInput, userAgent?: string, ipAddress?: string) {
 | 
				
			||||||
 | 
					    const passwordAuthEnabled = await this.configService.getValue("passwordAuthEnabled");
 | 
				
			||||||
 | 
					    if (passwordAuthEnabled === "false") {
 | 
				
			||||||
 | 
					      throw new Error("Password authentication is disabled. Please use an external authentication provider.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const user = await this.userRepository.findUserByEmailOrUsername(data.emailOrUsername);
 | 
					    const user = await this.userRepository.findUserByEmailOrUsername(data.emailOrUsername);
 | 
				
			||||||
    if (!user) {
 | 
					    if (!user) {
 | 
				
			||||||
      throw new Error("Invalid credentials");
 | 
					      throw new Error("Invalid credentials");
 | 
				
			||||||
@@ -146,6 +151,11 @@ export class AuthService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async requestPasswordReset(email: string, origin: string) {
 | 
					  async requestPasswordReset(email: string, origin: string) {
 | 
				
			||||||
 | 
					    const passwordAuthEnabled = await this.configService.getValue("passwordAuthEnabled");
 | 
				
			||||||
 | 
					    if (passwordAuthEnabled === "false") {
 | 
				
			||||||
 | 
					      throw new Error("Password authentication is disabled. Password reset is not available.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const user = await this.userRepository.findUserByEmail(email);
 | 
					    const user = await this.userRepository.findUserByEmail(email);
 | 
				
			||||||
    if (!user) {
 | 
					    if (!user) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@@ -171,6 +181,11 @@ export class AuthService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async resetPassword(token: string, newPassword: string) {
 | 
					  async resetPassword(token: string, newPassword: string) {
 | 
				
			||||||
 | 
					    const passwordAuthEnabled = await this.configService.getValue("passwordAuthEnabled");
 | 
				
			||||||
 | 
					    if (passwordAuthEnabled === "false") {
 | 
				
			||||||
 | 
					      throw new Error("Password authentication is disabled. Password reset is not available.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const resetRequest = await prisma.passwordReset.findFirst({
 | 
					    const resetRequest = await prisma.passwordReset.findFirst({
 | 
				
			||||||
      where: {
 | 
					      where: {
 | 
				
			||||||
        token,
 | 
					        token,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,26 @@ export class ConfigService {
 | 
				
			|||||||
    return config.value;
 | 
					    return config.value;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async setValue(key: string, value: string): Promise<void> {
 | 
				
			||||||
 | 
					    await prisma.appConfig.update({
 | 
				
			||||||
 | 
					      where: { key },
 | 
				
			||||||
 | 
					      data: { value },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async validatePasswordAuthDisable(): Promise<boolean> {
 | 
				
			||||||
 | 
					    const enabledProviders = await prisma.authProvider.findMany({
 | 
				
			||||||
 | 
					      where: { enabled: true },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return enabledProviders.length > 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async validateAllProvidersDisable(): Promise<boolean> {
 | 
				
			||||||
 | 
					    const passwordAuthEnabled = await this.getValue("passwordAuthEnabled");
 | 
				
			||||||
 | 
					    return passwordAuthEnabled === "true";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getGroupConfigs(group: string) {
 | 
					  async getGroupConfigs(group: string) {
 | 
				
			||||||
    const configs = await prisma.appConfig.findMany({
 | 
					    const configs = await prisma.appConfig.findMany({
 | 
				
			||||||
      where: { group },
 | 
					      where: { group },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "نسيت كلمة المرور",
 | 
					    "title": "نسيت كلمة المرور",
 | 
				
			||||||
    "description": "أدخل بريدك الإلكتروني وسنرسل لك تعليمات إعادة تعيين كلمة المرور.",
 | 
					    "description": "أدخل بريدك الإلكتروني وسنرسل لك تعليمات إعادة تعيين كلمة المرور.",
 | 
				
			||||||
    "resetInstructions": "تم إرسال تعليمات إعادة التعيين إلى بريدك الإلكتروني",
 | 
					    "resetInstructions": "تم إرسال تعليمات إعادة التعيين إلى بريدك الإلكتروني",
 | 
				
			||||||
    "pageTitle": "نسيت كلمة المرور"
 | 
					    "pageTitle": "نسيت كلمة المرور",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "تم تعطيل المصادقة بكلمة المرور. يرجى الاتصال بالمسؤول أو استخدام مزود مصادقة خارجي."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "إنشاء رابط المشاركة",
 | 
					    "generateTitle": "إنشاء رابط المشاركة",
 | 
				
			||||||
@@ -1130,6 +1131,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "الوثوق بالشهادات الموقعة ذاتياً",
 | 
					        "title": "الوثوق بالشهادات الموقعة ذاتياً",
 | 
				
			||||||
        "description": "قم بتمكين هذا للوثوق بشهادات SSL/TLS الموقعة ذاتياً (مفيد لبيئات التطوير)"
 | 
					        "description": "قم بتمكين هذا للوثوق بشهادات SSL/TLS الموقعة ذاتياً (مفيد لبيئات التطوير)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "المصادقة بالكلمة السرية",
 | 
				
			||||||
 | 
					        "description": "تمكين أو تعطيل المصادقة بالكلمة السرية"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1139,7 +1144,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "فشل في تحميل الإعدادات",
 | 
					      "loadFailed": "فشل في تحميل الإعدادات",
 | 
				
			||||||
      "updateFailed": "فشل في تحديث الإعدادات"
 | 
					      "updateFailed": "فشل في تحديث الإعدادات",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "لا يمكن تعطيل المصادقة بالكلمة السرية دون وجود على الأقل موفرين مصادقة مفعلين"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "لا توجد تغييرات للحفظ",
 | 
					      "noChanges": "لا توجد تغييرات للحفظ",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Passwort vergessen",
 | 
					    "title": "Passwort vergessen",
 | 
				
			||||||
    "description": "Geben Sie Ihre E-Mail-Adresse ein und wir senden Ihnen Anweisungen zum Zurücksetzen Ihres Passworts.",
 | 
					    "description": "Geben Sie Ihre E-Mail-Adresse ein und wir senden Ihnen Anweisungen zum Zurücksetzen Ihres Passworts.",
 | 
				
			||||||
    "resetInstructions": "Anweisungen zum Zurücksetzen wurden an Ihre E-Mail gesendet",
 | 
					    "resetInstructions": "Anweisungen zum Zurücksetzen wurden an Ihre E-Mail gesendet",
 | 
				
			||||||
    "pageTitle": "Passwort vergessen"
 | 
					    "pageTitle": "Passwort vergessen",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "Passwortauthentifizierung ist deaktiviert. Bitte kontaktieren Sie Ihren Administrator oder verwenden Sie einen externen Authentifizierungsanbieter."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Freigabe-Link generieren",
 | 
					    "generateTitle": "Freigabe-Link generieren",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
          "tls": "STARTTLS (Port 587)",
 | 
					          "tls": "STARTTLS (Port 587)",
 | 
				
			||||||
          "none": "Keine (Unsicher)"
 | 
					          "none": "Keine (Unsicher)"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Passwort-Authentifizierung",
 | 
				
			||||||
 | 
					        "description": "Passwort-basierte Authentifizierung aktivieren oder deaktivieren"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Fehler beim Laden der Einstellungen",
 | 
					      "loadFailed": "Fehler beim Laden der Einstellungen",
 | 
				
			||||||
      "updateFailed": "Fehler beim Aktualisieren der Einstellungen"
 | 
					      "updateFailed": "Fehler beim Aktualisieren der Einstellungen",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "Passwort-basierte Authentifizierung kann nicht deaktiviert werden, wenn kein aktiver Authentifizierungsanbieter vorhanden ist"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "Keine Änderungen zum Speichern",
 | 
					      "noChanges": "Keine Änderungen zum Speichern",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Forgot Password",
 | 
					    "title": "Forgot Password",
 | 
				
			||||||
    "description": "Enter your email address and we'll send you instructions to reset your password",
 | 
					    "description": "Enter your email address and we'll send you instructions to reset your password",
 | 
				
			||||||
    "resetInstructions": "Reset instructions sent to your email",
 | 
					    "resetInstructions": "Reset instructions sent to your email",
 | 
				
			||||||
    "pageTitle": "Forgot Password"
 | 
					    "pageTitle": "Forgot Password",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "Password authentication is disabled. Please contact your administrator or use an external authentication provider."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Generate Share Link",
 | 
					    "generateTitle": "Generate Share Link",
 | 
				
			||||||
@@ -1131,6 +1132,10 @@
 | 
				
			|||||||
      "serverUrl": {
 | 
					      "serverUrl": {
 | 
				
			||||||
        "title": "Server URL",
 | 
					        "title": "Server URL",
 | 
				
			||||||
        "description": "Base URL of the Palmr server (e.g.: https://palmr.example.com)"
 | 
					        "description": "Base URL of the Palmr server (e.g.: https://palmr.example.com)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Password Authentication",
 | 
				
			||||||
 | 
					        "description": "Enable or disable password-based authentication"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1140,7 +1145,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Failed to load settings",
 | 
					      "loadFailed": "Failed to load settings",
 | 
				
			||||||
      "updateFailed": "Failed to update settings"
 | 
					      "updateFailed": "Failed to update settings",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "Cannot disable password authentication without having at least one active authentication provider"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "No changes to save",
 | 
					      "noChanges": "No changes to save",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Recuperar contraseña",
 | 
					    "title": "Recuperar contraseña",
 | 
				
			||||||
    "description": "Introduce tu dirección de correo electrónico y te enviaremos instrucciones para restablecer tu contraseña.",
 | 
					    "description": "Introduce tu dirección de correo electrónico y te enviaremos instrucciones para restablecer tu contraseña.",
 | 
				
			||||||
    "resetInstructions": "Instrucciones de restablecimiento enviadas a tu correo electrónico",
 | 
					    "resetInstructions": "Instrucciones de restablecimiento enviadas a tu correo electrónico",
 | 
				
			||||||
    "pageTitle": "Recuperar contraseña"
 | 
					    "pageTitle": "Recuperar contraseña",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "La autenticación por contraseña está deshabilitada. Por favor, contacta a tu administrador o usa un proveedor de autenticación externo."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Generar enlace de compartir",
 | 
					    "generateTitle": "Generar enlace de compartir",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
          "tls": "STARTTLS (Puerto 587)",
 | 
					          "tls": "STARTTLS (Puerto 587)",
 | 
				
			||||||
          "none": "Ninguno (Inseguro)"
 | 
					          "none": "Ninguno (Inseguro)"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Autenticación por Contraseña",
 | 
				
			||||||
 | 
					        "description": "Habilitar o deshabilitar la autenticación basada en contraseña"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Error al cargar la configuración",
 | 
					      "loadFailed": "Error al cargar la configuración",
 | 
				
			||||||
      "updateFailed": "Error al actualizar la configuración"
 | 
					      "updateFailed": "Error al actualizar la configuración",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "No se puede deshabilitar la autenticación por contraseña sin tener al menos un proveedor de autenticación activo"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "No hay cambios para guardar",
 | 
					      "noChanges": "No hay cambios para guardar",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Mot de Passe Oublié",
 | 
					    "title": "Mot de Passe Oublié",
 | 
				
			||||||
    "description": "Entrez votre adresse email et nous vous enverrons les instructions pour réinitialiser votre mot de passe.",
 | 
					    "description": "Entrez votre adresse email et nous vous enverrons les instructions pour réinitialiser votre mot de passe.",
 | 
				
			||||||
    "resetInstructions": "Instructions de réinitialisation envoyées à votre email",
 | 
					    "resetInstructions": "Instructions de réinitialisation envoyées à votre email",
 | 
				
			||||||
    "pageTitle": "Mot de Passe Oublié"
 | 
					    "pageTitle": "Mot de Passe Oublié",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "L'authentification par mot de passe est désactivée. Veuillez contacter votre administrateur ou utiliser un fournisseur d'authentification externe."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Générer un lien de partage",
 | 
					    "generateTitle": "Générer un lien de partage",
 | 
				
			||||||
@@ -1131,6 +1132,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "Faire Confiance aux Certificats Auto-signés",
 | 
					        "title": "Faire Confiance aux Certificats Auto-signés",
 | 
				
			||||||
        "description": "Activez cette option pour faire confiance aux certificats SSL/TLS auto-signés (utile pour les environnements de développement)"
 | 
					        "description": "Activez cette option pour faire confiance aux certificats SSL/TLS auto-signés (utile pour les environnements de développement)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Authentification par Mot de Passe",
 | 
				
			||||||
 | 
					        "description": "Activer ou désactiver l'authentification basée sur mot de passe"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1140,7 +1145,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Échec du chargement des paramètres",
 | 
					      "loadFailed": "Échec du chargement des paramètres",
 | 
				
			||||||
      "updateFailed": "Échec de la mise à jour des paramètres"
 | 
					      "updateFailed": "Échec de la mise à jour des paramètres",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "Impossible de désactiver l'authentification par mot de passe sans avoir au moins un fournisseur d'authentification actif"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "Aucun changement à enregistrer",
 | 
					      "noChanges": "Aucun changement à enregistrer",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "पासवर्ड भूल गए",
 | 
					    "title": "पासवर्ड भूल गए",
 | 
				
			||||||
    "description": "अपना ईमेल पता दर्ज करें और हम आपको पासवर्ड रीसेट करने के निर्देश भेजेंगे।",
 | 
					    "description": "अपना ईमेल पता दर्ज करें और हम आपको पासवर्ड रीसेट करने के निर्देश भेजेंगे।",
 | 
				
			||||||
    "resetInstructions": "रीसेट निर्देश आपके ईमेल पर भेज दिए गए हैं",
 | 
					    "resetInstructions": "रीसेट निर्देश आपके ईमेल पर भेज दिए गए हैं",
 | 
				
			||||||
    "pageTitle": "पासवर्ड भूल गए"
 | 
					    "pageTitle": "पासवर्ड भूल गए",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "पासवर्ड ऑथेंटिकेशन अक्टिवेटेड है। कृपया अपने एडमिन से संपर्क करें या एक बाहरी ऑथेंटिकेशन प्रोवाइडर का उपयोग करें।"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "साझाकरण लिंक उत्पन्न करें",
 | 
					    "generateTitle": "साझाकरण लिंक उत्पन्न करें",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "स्व-हस्ताक्षरित प्रमाणपत्रों पर विश्वास करें",
 | 
					        "title": "स्व-हस्ताक्षरित प्रमाणपत्रों पर विश्वास करें",
 | 
				
			||||||
        "description": "स्व-हस्ताक्षरित SSL/TLS प्रमाणपत्रों पर विश्वास करने के लिए इसे सक्षम करें (विकास वातावरण के लिए उपयोगी)"
 | 
					        "description": "स्व-हस्ताक्षरित SSL/TLS प्रमाणपत्रों पर विश्वास करने के लिए इसे सक्षम करें (विकास वातावरण के लिए उपयोगी)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "पासवर्ड प्रमाणीकरण",
 | 
				
			||||||
 | 
					        "description": "पासवर्ड आधारित प्रमाणीकरण सक्षम या अक्षम करें"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "सेटिंग्स लोड करने में विफल",
 | 
					      "loadFailed": "सेटिंग्स लोड करने में विफल",
 | 
				
			||||||
      "updateFailed": "सेटिंग्स अपडेट करने में विफल"
 | 
					      "updateFailed": "सेटिंग्स अपडेट करने में विफल",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "कम से कम एक सक्रिय प्रमाणीकरण प्रदाता के बिना पासवर्ड प्रमाणीकरण अक्षम नहीं किया जा सकता"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "सहेजने के लिए कोई परिवर्तन नहीं",
 | 
					      "noChanges": "सहेजने के लिए कोई परिवर्तन नहीं",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Parola d'accesso Dimenticata",
 | 
					    "title": "Parola d'accesso Dimenticata",
 | 
				
			||||||
    "description": "Inserisci il tuo indirizzo email e ti invieremo le istruzioni per reimpostare la parola d'accesso.",
 | 
					    "description": "Inserisci il tuo indirizzo email e ti invieremo le istruzioni per reimpostare la parola d'accesso.",
 | 
				
			||||||
    "resetInstructions": "Istruzioni di reimpostazione inviate alla tua email",
 | 
					    "resetInstructions": "Istruzioni di reimpostazione inviate alla tua email",
 | 
				
			||||||
    "pageTitle": "Parola d'accesso Dimenticata"
 | 
					    "pageTitle": "Parola d'accesso Dimenticata",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "L'autenticazione tramite password è disabilitata. Contatta il tuo amministratore o utilizza un provider di autenticazione esterno."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Genera link di condivisione",
 | 
					    "generateTitle": "Genera link di condivisione",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "Accetta Certificati Auto-Firmati",
 | 
					        "title": "Accetta Certificati Auto-Firmati",
 | 
				
			||||||
        "description": "Abilita questa opzione per accettare certificati SSL/TLS auto-firmati (utile per ambienti di sviluppo)"
 | 
					        "description": "Abilita questa opzione per accettare certificati SSL/TLS auto-firmati (utile per ambienti di sviluppo)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Autenticazione Password",
 | 
				
			||||||
 | 
					        "description": "Abilita o disabilita l'autenticazione basata su password"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Errore durante il caricamento delle impostazioni",
 | 
					      "loadFailed": "Errore durante il caricamento delle impostazioni",
 | 
				
			||||||
      "updateFailed": "Errore durante l'aggiornamento delle impostazioni"
 | 
					      "updateFailed": "Errore durante l'aggiornamento delle impostazioni",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "Impossibile disabilitare l'autenticazione password senza avere almeno un provider di autenticazione attivo"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "Nessuna modifica da salvare",
 | 
					      "noChanges": "Nessuna modifica da salvare",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "パスワードをお忘れですか?",
 | 
					    "title": "パスワードをお忘れですか?",
 | 
				
			||||||
    "description": "メールアドレスを入力すると、パスワードリセットの指示を送信します。",
 | 
					    "description": "メールアドレスを入力すると、パスワードリセットの指示を送信します。",
 | 
				
			||||||
    "resetInstructions": "パスワードリセットの指示がメールに送信されました",
 | 
					    "resetInstructions": "パスワードリセットの指示がメールに送信されました",
 | 
				
			||||||
    "pageTitle": "パスワードをお忘れですか?"
 | 
					    "pageTitle": "パスワードをお忘れですか?",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "パスワード認証が無効になっています。管理者に連絡するか、外部認証プロバイダーを使用してください。"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "共有リンクを生成",
 | 
					    "generateTitle": "共有リンクを生成",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "自己署名証明書を信頼",
 | 
					        "title": "自己署名証明書を信頼",
 | 
				
			||||||
        "description": "自己署名SSL/TLS証明書を信頼するように設定します(開発環境で便利)"
 | 
					        "description": "自己署名SSL/TLS証明書を信頼するように設定します(開発環境で便利)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "パスワード認証",
 | 
				
			||||||
 | 
					        "description": "パスワード認証を有効または無効にする"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "設定の読み込みに失敗しました",
 | 
					      "loadFailed": "設定の読み込みに失敗しました",
 | 
				
			||||||
      "updateFailed": "設定の更新に失敗しました"
 | 
					      "updateFailed": "設定の更新に失敗しました",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "少なくとも1つのアクティブな認証プロバイダーがない場合、パスワード認証を無効にできません"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "保存する変更はありません",
 | 
					      "noChanges": "保存する変更はありません",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "비밀번호를 잊으셨나요?",
 | 
					    "title": "비밀번호를 잊으셨나요?",
 | 
				
			||||||
    "description": "이메일 주소를 입력하면 비밀번호 재설정 지침을 보내드립니다.",
 | 
					    "description": "이메일 주소를 입력하면 비밀번호 재설정 지침을 보내드립니다.",
 | 
				
			||||||
    "resetInstructions": "비밀번호 재설정 지침이 이메일로 전송되었습니다",
 | 
					    "resetInstructions": "비밀번호 재설정 지침이 이메일로 전송되었습니다",
 | 
				
			||||||
    "pageTitle": "비밀번호를 잊으셨나요?"
 | 
					    "pageTitle": "비밀번호를 잊으셨나요?",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "비밀번호 인증이 비활성화되어 있습니다. 관리자에게 문의하거나 외부 인증 공급자를 사용하세요."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "공유 링크 생성",
 | 
					    "generateTitle": "공유 링크 생성",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "자체 서명된 인증서 신뢰",
 | 
					        "title": "자체 서명된 인증서 신뢰",
 | 
				
			||||||
        "description": "자체 서명된 SSL/TLS 인증서를 신뢰하려면 활성화하세요 (개발 환경에서 유용)"
 | 
					        "description": "자체 서명된 SSL/TLS 인증서를 신뢰하려면 활성화하세요 (개발 환경에서 유용)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "비밀번호 인증",
 | 
				
			||||||
 | 
					        "description": "비밀번호 기반 인증 활성화 또는 비활성화"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "설정을 불러오는데 실패했습니다",
 | 
					      "loadFailed": "설정을 불러오는데 실패했습니다",
 | 
				
			||||||
      "updateFailed": "설정 업데이트에 실패했습니다"
 | 
					      "updateFailed": "설정 업데이트에 실패했습니다",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "최소 하나의 활성 인증 제공자가 없으면 비밀번호 인증을 비활성화할 수 없습니다"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "저장할 변경 사항이 없습니다",
 | 
					      "noChanges": "저장할 변경 사항이 없습니다",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Wachtwoord Vergeten",
 | 
					    "title": "Wachtwoord Vergeten",
 | 
				
			||||||
    "description": "Voer je e-mailadres in en we sturen je instructies om je wachtwoord te resetten.",
 | 
					    "description": "Voer je e-mailadres in en we sturen je instructies om je wachtwoord te resetten.",
 | 
				
			||||||
    "resetInstructions": "Reset instructies verzonden naar je e-mail",
 | 
					    "resetInstructions": "Reset instructies verzonden naar je e-mail",
 | 
				
			||||||
    "pageTitle": "Wachtwoord Vergeten"
 | 
					    "pageTitle": "Wachtwoord Vergeten",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "Wachtwoordauthenticatie is uitgeschakeld. Neem contact op met uw beheerder of gebruik een externe authenticatieprovider."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Deel-link genereren",
 | 
					    "generateTitle": "Deel-link genereren",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "Vertrouw Zelf-Ondertekende Certificaten",
 | 
					        "title": "Vertrouw Zelf-Ondertekende Certificaten",
 | 
				
			||||||
        "description": "Schakel dit in om zelf-ondertekende SSL/TLS certificaten te vertrouwen (handig voor ontwikkelomgevingen)"
 | 
					        "description": "Schakel dit in om zelf-ondertekende SSL/TLS certificaten te vertrouwen (handig voor ontwikkelomgevingen)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Wachtwoord Authenticatie",
 | 
				
			||||||
 | 
					        "description": "Wachtwoord-gebaseerde authenticatie inschakelen of uitschakelen"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Fout bij het laden van instellingen",
 | 
					      "loadFailed": "Fout bij het laden van instellingen",
 | 
				
			||||||
      "updateFailed": "Fout bij het bijwerken van instellingen"
 | 
					      "updateFailed": "Fout bij het bijwerken van instellingen",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "Wachtwoordauthenticatie kan niet worden uitgeschakeld zonder ten minste één actieve authenticatieprovider"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "Geen wijzigingen om op te slaan",
 | 
					      "noChanges": "Geen wijzigingen om op te slaan",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Zapomniałeś hasła?",
 | 
					    "title": "Zapomniałeś hasła?",
 | 
				
			||||||
    "description": "Wprowadź swój adres e-mail, a wyślemy Ci instrukcje resetowania hasła",
 | 
					    "description": "Wprowadź swój adres e-mail, a wyślemy Ci instrukcje resetowania hasła",
 | 
				
			||||||
    "resetInstructions": "Instrukcje resetowania wysłane na Twój adres e-mail",
 | 
					    "resetInstructions": "Instrukcje resetowania wysłane na Twój adres e-mail",
 | 
				
			||||||
    "pageTitle": "Zapomniałeś hasła?"
 | 
					    "pageTitle": "Zapomniałeś hasła?",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "Uwierzytelnianie hasłem jest wyłączone. Skontaktuj się z administratorem lub użyj zewnętrznego dostawcy uwierzytelniania."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Generuj link do udostępniania",
 | 
					    "generateTitle": "Generuj link do udostępniania",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "Zaufaj certyfikatom samopodpisanym",
 | 
					        "title": "Zaufaj certyfikatom samopodpisanym",
 | 
				
			||||||
        "description": "Włącz tę opcję, aby zaufać samopodpisanym certyfikatom SSL/TLS (przydatne w środowiskach deweloperskich)"
 | 
					        "description": "Włącz tę opcję, aby zaufać samopodpisanym certyfikatom SSL/TLS (przydatne w środowiskach deweloperskich)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Uwierzytelnianie hasłem",
 | 
				
			||||||
 | 
					        "description": "Włącz lub wyłącz uwierzytelnianie oparte na haśle"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Nie udało się załadować ustawień",
 | 
					      "loadFailed": "Nie udało się załadować ustawień",
 | 
				
			||||||
      "updateFailed": "Nie udało się zaktualizować ustawień"
 | 
					      "updateFailed": "Nie udało się zaktualizować ustawień",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "Uwierzytelnianie oparte na haśle nie może być wyłączone, jeśli nie ma co najmniej jednego aktywnego dostawcy uwierzytelniania"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "Brak zmian do zapisania",
 | 
					      "noChanges": "Brak zmian do zapisania",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Esqueceu a Senha",
 | 
					    "title": "Esqueceu a Senha",
 | 
				
			||||||
    "description": "Digite seu endereço de email e enviaremos instruções para redefinir sua senha.",
 | 
					    "description": "Digite seu endereço de email e enviaremos instruções para redefinir sua senha.",
 | 
				
			||||||
    "resetInstructions": "Instruções de redefinição enviadas para seu email",
 | 
					    "resetInstructions": "Instruções de redefinição enviadas para seu email",
 | 
				
			||||||
    "pageTitle": "Esqueceu a Senha"
 | 
					    "pageTitle": "Esqueceu a Senha",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "A autenticação por senha está desativada. Por favor, contate seu administrador ou use um provedor de autenticação externo."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Gerar link de compartilhamento",
 | 
					    "generateTitle": "Gerar link de compartilhamento",
 | 
				
			||||||
@@ -1136,6 +1137,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "Confiar em Certificados Auto-Assinados",
 | 
					        "title": "Confiar em Certificados Auto-Assinados",
 | 
				
			||||||
        "description": "Ative isso para confiar em certificados SSL/TLS auto-assinados (útil para ambientes de desenvolvimento)"
 | 
					        "description": "Ative isso para confiar em certificados SSL/TLS auto-assinados (útil para ambientes de desenvolvimento)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Autenticação por Senha",
 | 
				
			||||||
 | 
					        "description": "Ative ou desative a autenticação baseada em senha"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1145,7 +1150,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Falha ao carregar configurações",
 | 
					      "loadFailed": "Falha ao carregar configurações",
 | 
				
			||||||
      "updateFailed": "Falha ao atualizar configurações"
 | 
					      "updateFailed": "Falha ao atualizar configurações",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "Não é possível desabilitar a autenticação por senha sem ter pelo menos um provedor de autenticação ativo"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "Nenhuma alteração para salvar",
 | 
					      "noChanges": "Nenhuma alteração para salvar",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Забыли пароль",
 | 
					    "title": "Забыли пароль",
 | 
				
			||||||
    "description": "Введите адрес электронной почты, и мы отправим вам инструкции по сбросу пароля.",
 | 
					    "description": "Введите адрес электронной почты, и мы отправим вам инструкции по сбросу пароля.",
 | 
				
			||||||
    "resetInstructions": "Инструкции по сбросу отправлены на вашу электронную почту",
 | 
					    "resetInstructions": "Инструкции по сбросу отправлены на вашу электронную почту",
 | 
				
			||||||
    "pageTitle": "Забыли пароль"
 | 
					    "pageTitle": "Забыли пароль",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "Парольная аутентификация отключена. Пожалуйста, свяжитесь с администратором или используйте внешний провайдер аутентификации."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Создать ссылку для обмена",
 | 
					    "generateTitle": "Создать ссылку для обмена",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "Доверять самоподписанным сертификатам",
 | 
					        "title": "Доверять самоподписанным сертификатам",
 | 
				
			||||||
        "description": "Включите это для доверия самоподписанным SSL/TLS сертификатам (полезно для сред разработки)"
 | 
					        "description": "Включите это для доверия самоподписанным SSL/TLS сертификатам (полезно для сред разработки)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Парольная аутентификация",
 | 
				
			||||||
 | 
					        "description": "Включить или отключить парольную аутентификацию"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Ошибка загрузки настроек",
 | 
					      "loadFailed": "Ошибка загрузки настроек",
 | 
				
			||||||
      "updateFailed": "Ошибка обновления настроек"
 | 
					      "updateFailed": "Ошибка обновления настроек",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "Парольную аутентификацию нельзя отключить, если нет хотя бы одного активного поставщика аутентификации"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "Изменений для сохранения нет",
 | 
					      "noChanges": "Изменений для сохранения нет",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "Şifrenizi mi Unuttunuz?",
 | 
					    "title": "Şifrenizi mi Unuttunuz?",
 | 
				
			||||||
    "description": "E-posta adresinizi girin, şifre sıfırlama talimatlarını göndereceğiz.",
 | 
					    "description": "E-posta adresinizi girin, şifre sıfırlama talimatlarını göndereceğiz.",
 | 
				
			||||||
    "resetInstructions": "Şifre sıfırlama talimatları e-posta adresinize gönderildi",
 | 
					    "resetInstructions": "Şifre sıfırlama talimatları e-posta adresinize gönderildi",
 | 
				
			||||||
    "pageTitle": "Şifrenizi mi Unuttunuz?"
 | 
					    "pageTitle": "Şifrenizi mi Unuttunuz?",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "Şifre doğrulama devre dışı. Lütfen yöneticinize başvurun veya dış doğrulama sağlayıcısı kullanın."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "Paylaşım Bağlantısı Oluştur",
 | 
					    "generateTitle": "Paylaşım Bağlantısı Oluştur",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "Kendinden İmzalı Sertifikalara Güven",
 | 
					        "title": "Kendinden İmzalı Sertifikalara Güven",
 | 
				
			||||||
        "description": "Kendinden imzalı SSL/TLS sertifikalarına güvenmek için bunu etkinleştirin (geliştirme ortamları için kullanışlıdır)"
 | 
					        "description": "Kendinden imzalı SSL/TLS sertifikalarına güvenmek için bunu etkinleştirin (geliştirme ortamları için kullanışlıdır)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "Şifre Doğrulama",
 | 
				
			||||||
 | 
					        "description": "Şifre tabanlı doğrulamayı etkinleştirme veya devre dışı bırakma"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "Ayarlar yüklenemedi",
 | 
					      "loadFailed": "Ayarlar yüklenemedi",
 | 
				
			||||||
      "updateFailed": "Ayarlar güncellenemedi"
 | 
					      "updateFailed": "Ayarlar güncellenemedi",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "En az bir aktif kimlik doğrulama sağlayıcısı olmadan şifre doğrulaması devre dışı bırakılamaz"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "Kaydedilecek değişiklik yok",
 | 
					      "noChanges": "Kaydedilecek değişiklik yok",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,7 +313,8 @@
 | 
				
			|||||||
    "title": "忘记密码?",
 | 
					    "title": "忘记密码?",
 | 
				
			||||||
    "description": "请输入您的电子邮件,我们将发送密码重置指令给您。",
 | 
					    "description": "请输入您的电子邮件,我们将发送密码重置指令给您。",
 | 
				
			||||||
    "resetInstructions": "密码重置指令已发送到您的电子邮件",
 | 
					    "resetInstructions": "密码重置指令已发送到您的电子邮件",
 | 
				
			||||||
    "pageTitle": "忘记密码?"
 | 
					    "pageTitle": "忘记密码?",
 | 
				
			||||||
 | 
					    "passwordAuthDisabled": "密码认证已禁用。请联系您的管理员或使用外部认证提供商。"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "generateShareLink": {
 | 
					  "generateShareLink": {
 | 
				
			||||||
    "generateTitle": "生成分享链接",
 | 
					    "generateTitle": "生成分享链接",
 | 
				
			||||||
@@ -1128,6 +1129,10 @@
 | 
				
			|||||||
      "smtpTrustSelfSigned": {
 | 
					      "smtpTrustSelfSigned": {
 | 
				
			||||||
        "title": "信任自签名证书",
 | 
					        "title": "信任自签名证书",
 | 
				
			||||||
        "description": "启用此选项以信任自签名SSL/TLS证书(对开发环境有用)"
 | 
					        "description": "启用此选项以信任自签名SSL/TLS证书(对开发环境有用)"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "passwordAuthEnabled": {
 | 
				
			||||||
 | 
					        "title": "密码认证",
 | 
				
			||||||
 | 
					        "description": "启用或禁用基于密码的认证"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
@@ -1137,7 +1142,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "errors": {
 | 
					    "errors": {
 | 
				
			||||||
      "loadFailed": "加载设置失败",
 | 
					      "loadFailed": "加载设置失败",
 | 
				
			||||||
      "updateFailed": "更新设置失败"
 | 
					      "updateFailed": "更新设置失败",
 | 
				
			||||||
 | 
					      "passwordAuthRequiresProvider": "没有至少一个活动认证提供者时,无法禁用密码认证"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "messages": {
 | 
					    "messages": {
 | 
				
			||||||
      "noChanges": "没有需要保存的更改",
 | 
					      "noChanges": "没有需要保存的更改",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								apps/web/src/app/api/(proxy)/auth/config/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								apps/web/src/app/api/(proxy)/auth/config/route.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import { NextResponse } from "next/server";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const API_BASE_URL = process.env.API_BASE_URL || "http://localhost:3333";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function GET() {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const url = `${API_BASE_URL}/auth/config`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const apiRes = await fetch(url, {
 | 
				
			||||||
 | 
					      method: "GET",
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        "Content-Type": "application/json",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      redirect: "manual",
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const resBody = await apiRes.text();
 | 
				
			||||||
 | 
					    const res = new NextResponse(resBody, {
 | 
				
			||||||
 | 
					      status: apiRes.status,
 | 
				
			||||||
 | 
					      statusText: apiRes.statusText,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    apiRes.headers.forEach((value, key) => {
 | 
				
			||||||
 | 
					      res.headers.set(key, value);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.error("Error proxying auth config request:", error);
 | 
				
			||||||
 | 
					    return NextResponse.json({ error: "Internal server error" }, { status: 500 });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import { useRouter } from "next/navigation";
 | 
					import { useRouter } from "next/navigation";
 | 
				
			||||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
					import { zodResolver } from "@hookform/resolvers/zod";
 | 
				
			||||||
import axios from "axios";
 | 
					import axios from "axios";
 | 
				
			||||||
@@ -8,7 +9,7 @@ import { useForm } from "react-hook-form";
 | 
				
			|||||||
import { toast } from "sonner";
 | 
					import { toast } from "sonner";
 | 
				
			||||||
import { z } from "zod";
 | 
					import { z } from "zod";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { requestPasswordReset } from "@/http/endpoints";
 | 
					import { getAuthConfig, requestPasswordReset } from "@/http/endpoints";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ForgotPasswordFormData = {
 | 
					export type ForgotPasswordFormData = {
 | 
				
			||||||
  email: string;
 | 
					  email: string;
 | 
				
			||||||
@@ -17,16 +18,39 @@ export type ForgotPasswordFormData = {
 | 
				
			|||||||
export function useForgotPassword() {
 | 
					export function useForgotPassword() {
 | 
				
			||||||
  const t = useTranslations();
 | 
					  const t = useTranslations();
 | 
				
			||||||
  const router = useRouter();
 | 
					  const router = useRouter();
 | 
				
			||||||
 | 
					  const [passwordAuthEnabled, setPasswordAuthEnabled] = useState(true);
 | 
				
			||||||
 | 
					  const [authConfigLoading, setAuthConfigLoading] = useState(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const forgotPasswordSchema = z.object({
 | 
					  const forgotPasswordSchema = z.object({
 | 
				
			||||||
    email: z.string().email(t("validation.invalidEmail")),
 | 
					    email: z.string().email(t("validation.invalidEmail")),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const fetchAuthConfig = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const response = await getAuthConfig();
 | 
				
			||||||
 | 
					        setPasswordAuthEnabled((response as any).data.passwordAuthEnabled);
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error("Failed to fetch auth config:", error);
 | 
				
			||||||
 | 
					        setPasswordAuthEnabled(true);
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        setAuthConfigLoading(false);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetchAuthConfig();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const form = useForm<ForgotPasswordFormData>({
 | 
					  const form = useForm<ForgotPasswordFormData>({
 | 
				
			||||||
    resolver: zodResolver(forgotPasswordSchema),
 | 
					    resolver: zodResolver(forgotPasswordSchema),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onSubmit = async (data: ForgotPasswordFormData) => {
 | 
					  const onSubmit = async (data: ForgotPasswordFormData) => {
 | 
				
			||||||
 | 
					    if (!passwordAuthEnabled) {
 | 
				
			||||||
 | 
					      toast.error(t("errors.passwordAuthDisabled"));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await requestPasswordReset({
 | 
					      await requestPasswordReset({
 | 
				
			||||||
        email: data.email,
 | 
					        email: data.email,
 | 
				
			||||||
@@ -46,5 +70,7 @@ export function useForgotPassword() {
 | 
				
			|||||||
  return {
 | 
					  return {
 | 
				
			||||||
    form,
 | 
					    form,
 | 
				
			||||||
    onSubmit,
 | 
					    onSubmit,
 | 
				
			||||||
 | 
					    passwordAuthEnabled,
 | 
				
			||||||
 | 
					    authConfigLoading,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Link from "next/link";
 | 
				
			||||||
import { motion } from "framer-motion";
 | 
					import { motion } from "framer-motion";
 | 
				
			||||||
 | 
					import { useTranslations } from "next-intl";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { DefaultFooter } from "@/components/ui/default-footer";
 | 
					import { DefaultFooter } from "@/components/ui/default-footer";
 | 
				
			||||||
import { StaticBackgroundLights } from "../login/components/static-background-lights";
 | 
					import { StaticBackgroundLights } from "../login/components/static-background-lights";
 | 
				
			||||||
@@ -10,6 +12,7 @@ import { useForgotPassword } from "./hooks/use-forgot-password";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export default function ForgotPasswordPage() {
 | 
					export default function ForgotPasswordPage() {
 | 
				
			||||||
  const forgotPassword = useForgotPassword();
 | 
					  const forgotPassword = useForgotPassword();
 | 
				
			||||||
 | 
					  const t = useTranslations("ForgotPassword");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="relative flex min-h-screen flex-col">
 | 
					    <div className="relative flex min-h-screen flex-col">
 | 
				
			||||||
@@ -22,7 +25,24 @@ export default function ForgotPasswordPage() {
 | 
				
			|||||||
            initial={{ opacity: 0, y: 20 }}
 | 
					            initial={{ opacity: 0, y: 20 }}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <ForgotPasswordHeader />
 | 
					            <ForgotPasswordHeader />
 | 
				
			||||||
 | 
					            {forgotPassword.authConfigLoading ? (
 | 
				
			||||||
 | 
					              <div className="flex justify-center items-center py-8">
 | 
				
			||||||
 | 
					                <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            ) : !forgotPassword.passwordAuthEnabled ? (
 | 
				
			||||||
 | 
					              <div className="mt-8 space-y-4">
 | 
				
			||||||
 | 
					                <div className="text-center p-4 bg-muted/50 rounded-lg">
 | 
				
			||||||
 | 
					                  <p className="text-muted-foreground">{t("forgotPassword.passwordAuthDisabled")}</p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div className="text-center">
 | 
				
			||||||
 | 
					                  <Link className="text-muted-foreground hover:text-primary text-sm" href="/login">
 | 
				
			||||||
 | 
					                    {t("forgotPassword.backToLogin")}
 | 
				
			||||||
 | 
					                  </Link>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
              <ForgotPasswordForm form={forgotPassword.form} onSubmit={forgotPassword.onSubmit} />
 | 
					              <ForgotPasswordForm form={forgotPassword.form} onSubmit={forgotPassword.onSubmit} />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
          </motion.div>
 | 
					          </motion.div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import Link from "next/link";
 | 
					import Link from "next/link";
 | 
				
			||||||
import { zodResolver } from "@hookform/resolvers/zod";
 | 
					import { zodResolver } from "@hookform/resolvers/zod";
 | 
				
			||||||
import { useTranslations } from "next-intl";
 | 
					import { useTranslations } from "next-intl";
 | 
				
			||||||
@@ -6,6 +7,7 @@ import { useForm } from "react-hook-form";
 | 
				
			|||||||
import { Button } from "@/components/ui/button";
 | 
					import { Button } from "@/components/ui/button";
 | 
				
			||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 | 
					import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 | 
				
			||||||
import { Input } from "@/components/ui/input";
 | 
					import { Input } from "@/components/ui/input";
 | 
				
			||||||
 | 
					import { getEnabledProviders } from "@/http/endpoints";
 | 
				
			||||||
import { createLoginSchema, type LoginFormValues } from "../schemas/schema";
 | 
					import { createLoginSchema, type LoginFormValues } from "../schemas/schema";
 | 
				
			||||||
import { MultiProviderButtons } from "./multi-provider-buttons";
 | 
					import { MultiProviderButtons } from "./multi-provider-buttons";
 | 
				
			||||||
import { PasswordVisibilityToggle } from "./password-visibility-toggle";
 | 
					import { PasswordVisibilityToggle } from "./password-visibility-toggle";
 | 
				
			||||||
@@ -15,21 +17,50 @@ interface LoginFormProps {
 | 
				
			|||||||
  isVisible: boolean;
 | 
					  isVisible: boolean;
 | 
				
			||||||
  onToggleVisibility: () => void;
 | 
					  onToggleVisibility: () => void;
 | 
				
			||||||
  onSubmit: (data: LoginFormValues) => Promise<void>;
 | 
					  onSubmit: (data: LoginFormValues) => Promise<void>;
 | 
				
			||||||
 | 
					  passwordAuthEnabled: boolean;
 | 
				
			||||||
 | 
					  authConfigLoading: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function LoginForm({ error, isVisible, onToggleVisibility, onSubmit }: LoginFormProps) {
 | 
					export function LoginForm({
 | 
				
			||||||
 | 
					  error,
 | 
				
			||||||
 | 
					  isVisible,
 | 
				
			||||||
 | 
					  onToggleVisibility,
 | 
				
			||||||
 | 
					  onSubmit,
 | 
				
			||||||
 | 
					  passwordAuthEnabled,
 | 
				
			||||||
 | 
					  authConfigLoading,
 | 
				
			||||||
 | 
					}: LoginFormProps) {
 | 
				
			||||||
  const t = useTranslations();
 | 
					  const t = useTranslations();
 | 
				
			||||||
  const loginSchema = createLoginSchema(t);
 | 
					  const [hasEnabledProviders, setHasEnabledProviders] = useState(false);
 | 
				
			||||||
 | 
					  const [providersLoading, setProvidersLoading] = useState(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const loginSchema = createLoginSchema(t, passwordAuthEnabled);
 | 
				
			||||||
  const form = useForm<LoginFormValues>({
 | 
					  const form = useForm<LoginFormValues>({
 | 
				
			||||||
    resolver: zodResolver(loginSchema),
 | 
					    resolver: zodResolver(loginSchema),
 | 
				
			||||||
    defaultValues: {
 | 
					    defaultValues: {
 | 
				
			||||||
      emailOrUsername: "",
 | 
					      emailOrUsername: "",
 | 
				
			||||||
      password: "",
 | 
					      password: passwordAuthEnabled ? "" : undefined,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const isSubmitting = form.formState.isSubmitting;
 | 
					  const isSubmitting = form.formState.isSubmitting;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const checkProviders = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const response = await getEnabledProviders();
 | 
				
			||||||
 | 
					        const data = response.data as any;
 | 
				
			||||||
 | 
					        setHasEnabledProviders(data.success && data.data && data.data.length > 0);
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error("Error checking providers:", error);
 | 
				
			||||||
 | 
					        setHasEnabledProviders(false);
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        setProvidersLoading(false);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    checkProviders();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const renderErrorMessage = () =>
 | 
					  const renderErrorMessage = () =>
 | 
				
			||||||
    error && (
 | 
					    error && (
 | 
				
			||||||
      <p className="text-destructive text-sm text-center bg-destructive/10 p-2 rounded-md">
 | 
					      <p className="text-destructive text-sm text-center bg-destructive/10 p-2 rounded-md">
 | 
				
			||||||
@@ -84,13 +115,41 @@ export function LoginForm({ error, isVisible, onToggleVisibility, onSubmit }: Lo
 | 
				
			|||||||
    />
 | 
					    />
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (authConfigLoading || providersLoading) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div className="flex justify-center items-center py-8">
 | 
				
			||||||
 | 
					        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!passwordAuthEnabled && hasEnabledProviders) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <>
 | 
				
			||||||
 | 
					        {renderErrorMessage()}
 | 
				
			||||||
 | 
					        <MultiProviderButtons showSeparator={false} />
 | 
				
			||||||
 | 
					      </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!passwordAuthEnabled && !hasEnabledProviders) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <>
 | 
				
			||||||
 | 
					        {renderErrorMessage()}
 | 
				
			||||||
 | 
					        <div className="text-center py-8">
 | 
				
			||||||
 | 
					          <p className="text-destructive text-sm">{t("login.noAuthMethodsAvailable")}</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      {renderErrorMessage()}
 | 
					      {renderErrorMessage()}
 | 
				
			||||||
      <Form {...form}>
 | 
					      <Form {...form}>
 | 
				
			||||||
        <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4">
 | 
					        <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4">
 | 
				
			||||||
          {renderEmailOrUsernameField()}
 | 
					          {renderEmailOrUsernameField()}
 | 
				
			||||||
          {renderPasswordField()}
 | 
					          {passwordAuthEnabled && renderPasswordField()}
 | 
				
			||||||
          <Button className="w-full mt-4 cursor-pointer" variant="default" size="lg" type="submit">
 | 
					          <Button className="w-full mt-4 cursor-pointer" variant="default" size="lg" type="submit">
 | 
				
			||||||
            {isSubmitting ? t("login.signingIn") : t("login.signIn")}
 | 
					            {isSubmitting ? t("login.signingIn") : t("login.signIn")}
 | 
				
			||||||
          </Button>
 | 
					          </Button>
 | 
				
			||||||
@@ -99,11 +158,13 @@ export function LoginForm({ error, isVisible, onToggleVisibility, onSubmit }: Lo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      <MultiProviderButtons />
 | 
					      <MultiProviderButtons />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {passwordAuthEnabled && (
 | 
				
			||||||
        <div className="flex w-full items-center justify-center px-1 mt-2">
 | 
					        <div className="flex w-full items-center justify-center px-1 mt-2">
 | 
				
			||||||
          <Link className="text-muted-foreground hover:text-primary text-sm" href="/forgot-password">
 | 
					          <Link className="text-muted-foreground hover:text-primary text-sm" href="/forgot-password">
 | 
				
			||||||
            {t("login.forgotPassword")}
 | 
					            {t("login.forgotPassword")}
 | 
				
			||||||
          </Link>
 | 
					          </Link>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,11 @@ import { useAppInfo } from "@/contexts/app-info-context";
 | 
				
			|||||||
import { getEnabledProviders } from "@/http/endpoints";
 | 
					import { getEnabledProviders } from "@/http/endpoints";
 | 
				
			||||||
import type { EnabledAuthProvider } from "@/http/endpoints/auth/types";
 | 
					import type { EnabledAuthProvider } from "@/http/endpoints/auth/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function MultiProviderButtons() {
 | 
					interface MultiProviderButtonsProps {
 | 
				
			||||||
 | 
					  showSeparator?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function MultiProviderButtons({ showSeparator = true }: MultiProviderButtonsProps) {
 | 
				
			||||||
  const [providers, setProviders] = useState<EnabledAuthProvider[]>([]);
 | 
					  const [providers, setProviders] = useState<EnabledAuthProvider[]>([]);
 | 
				
			||||||
  const [loading, setLoading] = useState(true);
 | 
					  const [loading, setLoading] = useState(true);
 | 
				
			||||||
  const { firstAccess } = useAppInfo();
 | 
					  const { firstAccess } = useAppInfo();
 | 
				
			||||||
@@ -67,6 +71,7 @@ export function MultiProviderButtons() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="space-y-3">
 | 
					    <div className="space-y-3">
 | 
				
			||||||
 | 
					      {showSeparator && (
 | 
				
			||||||
        <div className="relative">
 | 
					        <div className="relative">
 | 
				
			||||||
          <div className="absolute inset-0 flex items-center">
 | 
					          <div className="absolute inset-0 flex items-center">
 | 
				
			||||||
            <span className="w-full border-t" />
 | 
					            <span className="w-full border-t" />
 | 
				
			||||||
@@ -75,6 +80,7 @@ export function MultiProviderButtons() {
 | 
				
			|||||||
            <span className="bg-background px-2 text-muted-foreground">Or continue with</span>
 | 
					            <span className="bg-background px-2 text-muted-foreground">Or continue with</span>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div className="space-y-2">
 | 
					      <div className="space-y-2">
 | 
				
			||||||
        {providers.map((provider) => (
 | 
					        {providers.map((provider) => (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ import { toast } from "sonner";
 | 
				
			|||||||
import { z } from "zod";
 | 
					import { z } from "zod";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useAuth } from "@/contexts/auth-context";
 | 
					import { useAuth } from "@/contexts/auth-context";
 | 
				
			||||||
import { getCurrentUser, login } from "@/http/endpoints";
 | 
					import { getAuthConfig, getCurrentUser, login } from "@/http/endpoints";
 | 
				
			||||||
import { completeTwoFactorLogin } from "@/http/endpoints/auth/two-factor";
 | 
					import { completeTwoFactorLogin } from "@/http/endpoints/auth/two-factor";
 | 
				
			||||||
import type { LoginResponse } from "@/http/endpoints/auth/two-factor/types";
 | 
					import type { LoginResponse } from "@/http/endpoints/auth/two-factor/types";
 | 
				
			||||||
import { LoginFormValues } from "../schemas/schema";
 | 
					import { LoginFormValues } from "../schemas/schema";
 | 
				
			||||||
@@ -31,6 +31,8 @@ export function useLogin() {
 | 
				
			|||||||
  const [twoFactorUserId, setTwoFactorUserId] = useState<string | null>(null);
 | 
					  const [twoFactorUserId, setTwoFactorUserId] = useState<string | null>(null);
 | 
				
			||||||
  const [twoFactorCode, setTwoFactorCode] = useState("");
 | 
					  const [twoFactorCode, setTwoFactorCode] = useState("");
 | 
				
			||||||
  const [isSubmitting, setIsSubmitting] = useState(false);
 | 
					  const [isSubmitting, setIsSubmitting] = useState(false);
 | 
				
			||||||
 | 
					  const [passwordAuthEnabled, setPasswordAuthEnabled] = useState(true);
 | 
				
			||||||
 | 
					  const [authConfigLoading, setAuthConfigLoading] = useState(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    const errorParam = searchParams.get("error");
 | 
					    const errorParam = searchParams.get("error");
 | 
				
			||||||
@@ -60,6 +62,22 @@ export function useLogin() {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [searchParams, t]);
 | 
					  }, [searchParams, t]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const fetchAuthConfig = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const response = await getAuthConfig();
 | 
				
			||||||
 | 
					        setPasswordAuthEnabled((response as any).data.passwordAuthEnabled);
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.error("Failed to fetch auth config:", error);
 | 
				
			||||||
 | 
					        setPasswordAuthEnabled(true);
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        setAuthConfigLoading(false);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetchAuthConfig();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggleVisibility = () => setIsVisible(!isVisible);
 | 
					  const toggleVisibility = () => setIsVisible(!isVisible);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onSubmit = async (data: LoginFormValues) => {
 | 
					  const onSubmit = async (data: LoginFormValues) => {
 | 
				
			||||||
@@ -67,7 +85,12 @@ export function useLogin() {
 | 
				
			|||||||
    setIsSubmitting(true);
 | 
					    setIsSubmitting(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const response = await login(data);
 | 
					      if (!passwordAuthEnabled) {
 | 
				
			||||||
 | 
					        setError(t("errors.passwordAuthDisabled"));
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const response = await login(data as any);
 | 
				
			||||||
      const loginData = response.data as LoginResponse;
 | 
					      const loginData = response.data as LoginResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (loginData.requiresTwoFactor && loginData.userId) {
 | 
					      if (loginData.requiresTwoFactor && loginData.userId) {
 | 
				
			||||||
@@ -77,7 +100,6 @@ export function useLogin() {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (loginData.user) {
 | 
					      if (loginData.user) {
 | 
				
			||||||
        // Após login bem-sucedido, buscar dados completos do usuário incluindo a imagem
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          const userResponse = await getCurrentUser();
 | 
					          const userResponse = await getCurrentUser();
 | 
				
			||||||
          if (userResponse?.data?.user) {
 | 
					          if (userResponse?.data?.user) {
 | 
				
			||||||
@@ -92,7 +114,6 @@ export function useLogin() {
 | 
				
			|||||||
          console.warn("Failed to fetch complete user data, using login data:", userErr);
 | 
					          console.warn("Failed to fetch complete user data, using login data:", userErr);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Fallback para dados do login se falhar ao buscar dados completos
 | 
					 | 
				
			||||||
        const { isAdmin, ...userData } = loginData.user;
 | 
					        const { isAdmin, ...userData } = loginData.user;
 | 
				
			||||||
        setUser({ ...userData, image: null });
 | 
					        setUser({ ...userData, image: null });
 | 
				
			||||||
        setIsAdmin(isAdmin);
 | 
					        setIsAdmin(isAdmin);
 | 
				
			||||||
@@ -129,7 +150,6 @@ export function useLogin() {
 | 
				
			|||||||
        rememberDevice: rememberDevice,
 | 
					        rememberDevice: rememberDevice,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Após two-factor login bem-sucedido, buscar dados completos do usuário incluindo a imagem
 | 
					 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const userResponse = await getCurrentUser();
 | 
					        const userResponse = await getCurrentUser();
 | 
				
			||||||
        if (userResponse?.data?.user) {
 | 
					        if (userResponse?.data?.user) {
 | 
				
			||||||
@@ -144,7 +164,6 @@ export function useLogin() {
 | 
				
			|||||||
        console.warn("Failed to fetch complete user data after 2FA, using response data:", userErr);
 | 
					        console.warn("Failed to fetch complete user data after 2FA, using response data:", userErr);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Fallback para dados da resposta se falhar ao buscar dados completos
 | 
					 | 
				
			||||||
      const { isAdmin, ...userData } = response.data.user;
 | 
					      const { isAdmin, ...userData } = response.data.user;
 | 
				
			||||||
      setUser({ ...userData, image: userData.image ?? null });
 | 
					      setUser({ ...userData, image: userData.image ?? null });
 | 
				
			||||||
      setIsAdmin(isAdmin);
 | 
					      setIsAdmin(isAdmin);
 | 
				
			||||||
@@ -172,5 +191,7 @@ export function useLogin() {
 | 
				
			|||||||
    setTwoFactorCode,
 | 
					    setTwoFactorCode,
 | 
				
			||||||
    onTwoFactorSubmit,
 | 
					    onTwoFactorSubmit,
 | 
				
			||||||
    isSubmitting,
 | 
					    isSubmitting,
 | 
				
			||||||
 | 
					    passwordAuthEnabled,
 | 
				
			||||||
 | 
					    authConfigLoading,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,6 +53,8 @@ export default function LoginPage() {
 | 
				
			|||||||
                isVisible={login.isVisible}
 | 
					                isVisible={login.isVisible}
 | 
				
			||||||
                onSubmit={login.onSubmit}
 | 
					                onSubmit={login.onSubmit}
 | 
				
			||||||
                onToggleVisibility={login.toggleVisibility}
 | 
					                onToggleVisibility={login.toggleVisibility}
 | 
				
			||||||
 | 
					                passwordAuthEnabled={login.passwordAuthEnabled}
 | 
				
			||||||
 | 
					                authConfigLoading={login.authConfigLoading}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
          </motion.div>
 | 
					          </motion.div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,10 @@ import * as z from "zod";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type TFunction = ReturnType<typeof useTranslations>;
 | 
					type TFunction = ReturnType<typeof useTranslations>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createLoginSchema = (t: TFunction) =>
 | 
					export const createLoginSchema = (t: TFunction, passwordAuthEnabled: boolean = true) =>
 | 
				
			||||||
  z.object({
 | 
					  z.object({
 | 
				
			||||||
    emailOrUsername: z.string().min(1, t("validation.emailOrUsernameRequired")),
 | 
					    emailOrUsername: z.string().min(1, t("validation.emailOrUsernameRequired")),
 | 
				
			||||||
    password: z.string().min(1, t("validation.passwordRequired")),
 | 
					    password: passwordAuthEnabled ? z.string().min(1, t("validation.passwordRequired")) : z.string().optional(),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type LoginFormValues = z.infer<ReturnType<typeof createLoginSchema>>;
 | 
					export type LoginFormValues = z.infer<ReturnType<typeof createLoginSchema>>;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -172,9 +172,20 @@ export function useSettings() {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await refreshAppInfo();
 | 
					      await refreshAppInfo();
 | 
				
			||||||
    } catch {
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      const errorMessage = error?.response?.data?.error || error?.message || "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        errorMessage.includes("autenticação por senha") ||
 | 
				
			||||||
 | 
					        errorMessage.includes("provedor de autenticação ativo") ||
 | 
				
			||||||
 | 
					        errorMessage.includes("password authentication") ||
 | 
				
			||||||
 | 
					        errorMessage.includes("authentication provider")
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        toast.error(t("settings.errors.passwordAuthRequiresProvider"));
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
        toast.error(t("settings.errors.updateFailed"));
 | 
					        toast.error(t("settings.errors.updateFailed"));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const toggleCollapse = (group: string) => {
 | 
					  const toggleCollapse = (group: string) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -99,3 +99,9 @@ export const updateProvidersOrder = <TData = UpdateProvidersOrderResult>(
 | 
				
			|||||||
): Promise<TData> => {
 | 
					): Promise<TData> => {
 | 
				
			||||||
  return apiInstance.put(`/api/auth/providers/order`, updateProvidersOrderBody, options);
 | 
					  return apiInstance.put(`/api/auth/providers/order`, updateProvidersOrderBody, options);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getAuthConfig = <TData = { passwordAuthEnabled: boolean }>(
 | 
				
			||||||
 | 
					  options?: AxiosRequestConfig
 | 
				
			||||||
 | 
					): Promise<TData> => {
 | 
				
			||||||
 | 
					  return apiInstance.get(`/api/auth/config`, options);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user