mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
feat: enhance authentication flow and user redirection (#183)
This commit is contained in:
@@ -113,14 +113,21 @@ export class AuthController {
|
|||||||
|
|
||||||
async getCurrentUser(request: FastifyRequest, reply: FastifyReply) {
|
async getCurrentUser(request: FastifyRequest, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const userId = (request as any).user?.userId;
|
let userId: string | null = null;
|
||||||
|
try {
|
||||||
|
await request.jwtVerify();
|
||||||
|
userId = (request as any).user?.userId;
|
||||||
|
} catch (err) {
|
||||||
|
return reply.send({ user: null });
|
||||||
|
}
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return reply.status(401).send({ error: "Unauthorized: a valid token is required to access this resource." });
|
return reply.send({ user: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.authService.getUserById(userId);
|
const user = await this.authService.getUserById(userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return reply.status(404).send({ error: "User not found" });
|
return reply.send({ user: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
return reply.send({ user });
|
return reply.send({ user });
|
||||||
|
@@ -153,33 +153,29 @@ export async function authRoutes(app: FastifyInstance) {
|
|||||||
tags: ["Authentication"],
|
tags: ["Authentication"],
|
||||||
operationId: "getCurrentUser",
|
operationId: "getCurrentUser",
|
||||||
summary: "Get Current User",
|
summary: "Get Current User",
|
||||||
description: "Returns the current authenticated user's information",
|
description: "Returns the current authenticated user's information or null if not authenticated",
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.union([
|
||||||
user: z.object({
|
z.object({
|
||||||
id: z.string().describe("User ID"),
|
user: z.object({
|
||||||
firstName: z.string().describe("User first name"),
|
id: z.string().describe("User ID"),
|
||||||
lastName: z.string().describe("User last name"),
|
firstName: z.string().describe("User first name"),
|
||||||
username: z.string().describe("User username"),
|
lastName: z.string().describe("User last name"),
|
||||||
email: z.string().email().describe("User email"),
|
username: z.string().describe("User username"),
|
||||||
image: z.string().nullable().describe("User profile image URL"),
|
email: z.string().email().describe("User email"),
|
||||||
isAdmin: z.boolean().describe("User is admin"),
|
image: z.string().nullable().describe("User profile image URL"),
|
||||||
isActive: z.boolean().describe("User is active"),
|
isAdmin: z.boolean().describe("User is admin"),
|
||||||
createdAt: z.date().describe("User creation date"),
|
isActive: z.boolean().describe("User is active"),
|
||||||
updatedAt: z.date().describe("User last update date"),
|
createdAt: z.date().describe("User creation date"),
|
||||||
|
updatedAt: z.date().describe("User last update date"),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
z.object({
|
||||||
401: z.object({ error: z.string().describe("Error message") }),
|
user: z.null().describe("No user when not authenticated"),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
preValidation: async (request: FastifyRequest, reply: FastifyReply) => {
|
|
||||||
try {
|
|
||||||
await request.jwtVerify();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
reply.status(401).send({ error: "Unauthorized: a valid token is required to access this resource." });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
authController.getCurrentUser.bind(authController)
|
authController.getCurrentUser.bind(authController)
|
||||||
);
|
);
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "الملف الشخصي"
|
"pageTitle": "الملف الشخصي"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "مشاركة رمز QR",
|
||||||
|
"description": "امسح رمز QR هذا للوصول إلى الرابط.",
|
||||||
|
"download": "تحميل رمز QR"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "ملفاتي",
|
"title": "ملفاتي",
|
||||||
@@ -1746,10 +1751,5 @@
|
|||||||
"passwordRequired": "كلمة المرور مطلوبة",
|
"passwordRequired": "كلمة المرور مطلوبة",
|
||||||
"nameRequired": "الاسم مطلوب",
|
"nameRequired": "الاسم مطلوب",
|
||||||
"required": "هذا الحقل مطلوب"
|
"required": "هذا الحقل مطلوب"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "مشاركة رمز QR",
|
|
||||||
"description": "امسح رمز QR هذا للوصول إلى الرابط.",
|
|
||||||
"download": "تحميل رمز QR"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Profil"
|
"pageTitle": "Profil"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "QR-Code teilen",
|
||||||
|
"description": "Scannen Sie diesen QR-Code, um auf den Link zuzugreifen.",
|
||||||
|
"download": "QR-Code herunterladen"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Meine Dateien",
|
"title": "Meine Dateien",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordRequired": "Passwort ist erforderlich",
|
"passwordRequired": "Passwort ist erforderlich",
|
||||||
"nameRequired": "Name ist erforderlich",
|
"nameRequired": "Name ist erforderlich",
|
||||||
"required": "Dieses Feld ist erforderlich"
|
"required": "Dieses Feld ist erforderlich"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "QR-Code teilen",
|
|
||||||
"description": "Scannen Sie diesen QR-Code, um auf den Link zuzugreifen.",
|
|
||||||
"download": "QR-Code herunterladen"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Perfil"
|
"pageTitle": "Perfil"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "Compartir Código QR",
|
||||||
|
"description": "Escanea este código QR para acceder al enlace.",
|
||||||
|
"download": "Descargar Código QR"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Mis archivos",
|
"title": "Mis archivos",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordRequired": "Se requiere la contraseña",
|
"passwordRequired": "Se requiere la contraseña",
|
||||||
"nameRequired": "El nombre es obligatorio",
|
"nameRequired": "El nombre es obligatorio",
|
||||||
"required": "Este campo es obligatorio"
|
"required": "Este campo es obligatorio"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "Compartir Código QR",
|
|
||||||
"description": "Escanea este código QR para acceder al enlace.",
|
|
||||||
"download": "Descargar Código QR"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Profil"
|
"pageTitle": "Profil"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "Code QR de Partage",
|
||||||
|
"description": "Scannez ce code QR pour accéder au lien.",
|
||||||
|
"download": "Télécharger le Code QR"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Mes Fichiers",
|
"title": "Mes Fichiers",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordRequired": "Le mot de passe est requis",
|
"passwordRequired": "Le mot de passe est requis",
|
||||||
"nameRequired": "Nome é obrigatório",
|
"nameRequired": "Nome é obrigatório",
|
||||||
"required": "Este campo é obrigatório"
|
"required": "Este campo é obrigatório"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "Code QR de Partage",
|
|
||||||
"description": "Scannez ce code QR pour accéder au lien.",
|
|
||||||
"download": "Télécharger le Code QR"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "प्रोफ़ाइल"
|
"pageTitle": "प्रोफ़ाइल"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "QR कोड साझा करें",
|
||||||
|
"description": "इस QR कोड को स्कैन करके लिंक तक पहुंच सकते हैं।",
|
||||||
|
"download": "QR कोड डाउनलोड करें"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "मेरी फाइलें",
|
"title": "मेरी फाइलें",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordRequired": "पासवर्ड आवश्यक है",
|
"passwordRequired": "पासवर्ड आवश्यक है",
|
||||||
"nameRequired": "नाम आवश्यक है",
|
"nameRequired": "नाम आवश्यक है",
|
||||||
"required": "यह फ़ील्ड आवश्यक है"
|
"required": "यह फ़ील्ड आवश्यक है"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "QR कोड साझा करें",
|
|
||||||
"description": "इस QR कोड को स्कैन करके लिंक तक पहुंच सकते हैं।",
|
|
||||||
"download": "QR कोड डाउनलोड करें"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Profilo"
|
"pageTitle": "Profilo"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "Condividi QR Code",
|
||||||
|
"description": "Scansiona questo codice QR per accedere al link.",
|
||||||
|
"download": "Scarica QR Code"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "I Miei File",
|
"title": "I Miei File",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordMinLength": "La password deve contenere almeno 6 caratteri",
|
"passwordMinLength": "La password deve contenere almeno 6 caratteri",
|
||||||
"nameRequired": "Il nome è obbligatorio",
|
"nameRequired": "Il nome è obbligatorio",
|
||||||
"required": "Questo campo è obbligatorio"
|
"required": "Questo campo è obbligatorio"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "Condividi QR Code",
|
|
||||||
"description": "Scansiona questo codice QR per accedere al link.",
|
|
||||||
"download": "Scarica QR Code"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "プロフィール"
|
"pageTitle": "プロフィール"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "QRコードを共有",
|
||||||
|
"description": "このQRコードをスキャンしてリンクにアクセスしてください。",
|
||||||
|
"download": "QRコードをダウンロード"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "マイファイル",
|
"title": "マイファイル",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordRequired": "パスワードは必須です",
|
"passwordRequired": "パスワードは必須です",
|
||||||
"nameRequired": "名前は必須です",
|
"nameRequired": "名前は必須です",
|
||||||
"required": "このフィールドは必須です"
|
"required": "このフィールドは必須です"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "QRコードを共有",
|
|
||||||
"description": "このQRコードをスキャンしてリンクにアクセスしてください。",
|
|
||||||
"download": "QRコードをダウンロード"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "프로필"
|
"pageTitle": "프로필"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "QR 코드 공유",
|
||||||
|
"description": "이 QR 코드를 스캔하여 링크에 접근할 수 있습니다.",
|
||||||
|
"download": "QR 코드 다운로드"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "내 파일",
|
"title": "내 파일",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordRequired": "비밀번호는 필수입니다",
|
"passwordRequired": "비밀번호는 필수입니다",
|
||||||
"nameRequired": "이름은 필수입니다",
|
"nameRequired": "이름은 필수입니다",
|
||||||
"required": "이 필드는 필수입니다"
|
"required": "이 필드는 필수입니다"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "QR 코드 공유",
|
|
||||||
"description": "이 QR 코드를 스캔하여 링크에 접근할 수 있습니다.",
|
|
||||||
"download": "QR 코드 다운로드"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Profiel"
|
"pageTitle": "Profiel"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "QR Code Delen",
|
||||||
|
"description": "Scan deze QR-code om toegang te krijgen tot de link.",
|
||||||
|
"download": "QR Code Downloaden"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Mijn Bestanden",
|
"title": "Mijn Bestanden",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordMinLength": "Wachtwoord moet minimaal 6 tekens bevatten",
|
"passwordMinLength": "Wachtwoord moet minimaal 6 tekens bevatten",
|
||||||
"nameRequired": "Naam is verplicht",
|
"nameRequired": "Naam is verplicht",
|
||||||
"required": "Dit veld is verplicht"
|
"required": "Dit veld is verplicht"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "QR Code Delen",
|
|
||||||
"description": "Scan deze QR-code om toegang te krijgen tot de link.",
|
|
||||||
"download": "QR Code Downloaden"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Profil"
|
"pageTitle": "Profil"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "Udostępnij kod QR",
|
||||||
|
"description": "Skanuj ten kod QR, aby uzyskać dostęp do linku.",
|
||||||
|
"download": "Pobierz kod QR"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Moje pliki",
|
"title": "Moje pliki",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordMinLength": "Hasło musi mieć co najmniej 6 znaków",
|
"passwordMinLength": "Hasło musi mieć co najmniej 6 znaków",
|
||||||
"nameRequired": "Nazwa jest wymagana",
|
"nameRequired": "Nazwa jest wymagana",
|
||||||
"required": "To pole jest wymagane"
|
"required": "To pole jest wymagane"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "Udostępnij kod QR",
|
|
||||||
"description": "Skanuj ten kod QR, aby uzyskać dostęp do linku.",
|
|
||||||
"download": "Pobierz kod QR"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Perfil"
|
"pageTitle": "Perfil"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "Compartilhar QR Code",
|
||||||
|
"description": "Escaneie este código QR para acessar o link.",
|
||||||
|
"download": "Baixar QR Code"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Meus Arquivos",
|
"title": "Meus Arquivos",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"lastNameRequired": "O sobrenome é necessário",
|
"lastNameRequired": "O sobrenome é necessário",
|
||||||
"usernameLength": "O nome de usuário deve ter pelo menos 3 caracteres",
|
"usernameLength": "O nome de usuário deve ter pelo menos 3 caracteres",
|
||||||
"usernameSpaces": "O nome de usuário não pode conter espaços"
|
"usernameSpaces": "O nome de usuário não pode conter espaços"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "Compartilhar QR Code",
|
|
||||||
"description": "Escaneie este código QR para acessar o link.",
|
|
||||||
"download": "Baixar QR Code"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Профиль"
|
"pageTitle": "Профиль"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "Поделиться QR-кодом",
|
||||||
|
"description": "Отсканируйте этот QR-код, чтобы получить доступ к ссылке.",
|
||||||
|
"download": "Скачать QR-код"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Мои файлы",
|
"title": "Мои файлы",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordRequired": "Требуется пароль",
|
"passwordRequired": "Требуется пароль",
|
||||||
"nameRequired": "Требуется имя",
|
"nameRequired": "Требуется имя",
|
||||||
"required": "Это поле обязательно"
|
"required": "Это поле обязательно"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "Поделиться QR-кодом",
|
|
||||||
"description": "Отсканируйте этот QR-код, чтобы получить доступ к ссылке.",
|
|
||||||
"download": "Скачать QR-код"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "Profil"
|
"pageTitle": "Profil"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "QR Kodu Paylaş",
|
||||||
|
"description": "Bu QR kodu tarayarak bağlantıya erişebilirsiniz.",
|
||||||
|
"download": "QR Kodu İndir"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "Benim Dosyalarım",
|
"title": "Benim Dosyalarım",
|
||||||
@@ -1744,10 +1749,5 @@
|
|||||||
"passwordRequired": "Şifre gerekli",
|
"passwordRequired": "Şifre gerekli",
|
||||||
"nameRequired": "İsim gereklidir",
|
"nameRequired": "İsim gereklidir",
|
||||||
"required": "Bu alan zorunludur"
|
"required": "Bu alan zorunludur"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "QR Kodu Paylaş",
|
|
||||||
"description": "Bu QR kodu tarayarak bağlantıya erişebilirsiniz.",
|
|
||||||
"download": "QR Kodu İndir"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -455,6 +455,11 @@
|
|||||||
},
|
},
|
||||||
"pageTitle": "个人资料"
|
"pageTitle": "个人资料"
|
||||||
},
|
},
|
||||||
|
"qrCodeModal": {
|
||||||
|
"title": "分享QR Code",
|
||||||
|
"description": "扫描此QR Code以访问链接。",
|
||||||
|
"download": "下载QR Code"
|
||||||
|
},
|
||||||
"quickAccess": {
|
"quickAccess": {
|
||||||
"files": {
|
"files": {
|
||||||
"title": "我的文件",
|
"title": "我的文件",
|
||||||
@@ -1507,11 +1512,7 @@
|
|||||||
"copyToClipboard": "复制到剪贴板",
|
"copyToClipboard": "复制到剪贴板",
|
||||||
"savedMessage": "我已保存备用码",
|
"savedMessage": "我已保存备用码",
|
||||||
"available": "可用备用码:{count}个",
|
"available": "可用备用码:{count}个",
|
||||||
"instructions": [
|
"instructions": ["• 将这些代码保存在安全的位置", "• 每个备用码只能使用一次", "• 您可以随时生成新的备用码"]
|
||||||
"• 将这些代码保存在安全的位置",
|
|
||||||
"• 每个备用码只能使用一次",
|
|
||||||
"• 您可以随时生成新的备用码"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"verification": {
|
"verification": {
|
||||||
"title": "双重认证",
|
"title": "双重认证",
|
||||||
@@ -1744,10 +1745,5 @@
|
|||||||
"passwordRequired": "密码为必填项",
|
"passwordRequired": "密码为必填项",
|
||||||
"nameRequired": "名称为必填项",
|
"nameRequired": "名称为必填项",
|
||||||
"required": "此字段为必填项"
|
"required": "此字段为必填项"
|
||||||
},
|
|
||||||
"qrCodeModal": {
|
|
||||||
"title": "分享QR Code",
|
|
||||||
"description": "扫描此QR Code以访问链接。",
|
|
||||||
"download": "下载QR Code"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,34 +4,51 @@ import { useEffect } from "react";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
import { useAuth } from "@/contexts/auth-context";
|
||||||
import { useSecureConfigValue } from "@/hooks/use-secure-configs";
|
import { useSecureConfigValue } from "@/hooks/use-secure-configs";
|
||||||
|
|
||||||
interface HomeStore {
|
interface HomeStore {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
shouldShowHomePage: boolean;
|
||||||
setIsLoading: (loading: boolean) => void;
|
setIsLoading: (loading: boolean) => void;
|
||||||
|
setShouldShowHomePage: (show: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useHomeStore = create<HomeStore>((set) => ({
|
const useHomeStore = create<HomeStore>((set) => ({
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
shouldShowHomePage: false,
|
||||||
setIsLoading: (loading: boolean) => set({ isLoading: loading }),
|
setIsLoading: (loading: boolean) => set({ isLoading: loading }),
|
||||||
|
setShouldShowHomePage: (show: boolean) => set({ shouldShowHomePage: show }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export function useHome() {
|
export function useHome() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isLoading, setIsLoading } = useHomeStore();
|
const { isLoading, shouldShowHomePage, setIsLoading, setShouldShowHomePage } = useHomeStore();
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
const { value: showHomePage, isLoading: configLoading } = useSecureConfigValue("showHomePage");
|
const { value: showHomePage, isLoading: configLoading } = useSecureConfigValue("showHomePage");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!configLoading) {
|
if (isAuthenticated === true) {
|
||||||
|
router.replace("/dashboard");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, router]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!configLoading && isAuthenticated !== null) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
if (showHomePage !== "true") {
|
if (showHomePage !== "true") {
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
|
setShouldShowHomePage(false);
|
||||||
|
} else if (isAuthenticated === false) {
|
||||||
|
setShouldShowHomePage(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [router, showHomePage, configLoading, setIsLoading]);
|
}, [router, showHomePage, configLoading, isAuthenticated, setIsLoading, setShouldShowHomePage]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading,
|
isLoading,
|
||||||
|
shouldShowHomePage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -7,9 +7,9 @@ import { Navbar } from "./components/navbar";
|
|||||||
import { useHome } from "./hooks/use-home";
|
import { useHome } from "./hooks/use-home";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const { isLoading } = useHome();
|
const { isLoading, shouldShowHomePage } = useHome();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || !shouldShowHomePage) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import { getLocale } from "next-intl/server";
|
|||||||
|
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
|
import { RedirectHandler } from "@/components/auth/redirect-handler";
|
||||||
import { Favicon } from "@/components/layout/favicon";
|
import { Favicon } from "@/components/layout/favicon";
|
||||||
import { DynamicToaster } from "@/components/ui/dynamic-toaster";
|
import { DynamicToaster } from "@/components/ui/dynamic-toaster";
|
||||||
import { useAppInfo } from "@/contexts/app-info-context";
|
import { useAppInfo } from "@/contexts/app-info-context";
|
||||||
@@ -39,7 +40,9 @@ export default async function RootLayout({
|
|||||||
<NextIntlClientProvider>
|
<NextIntlClientProvider>
|
||||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<ShareProvider>{children}</ShareProvider>
|
<RedirectHandler>
|
||||||
|
<ShareProvider>{children}</ShareProvider>
|
||||||
|
</RedirectHandler>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
<DynamicToaster />
|
<DynamicToaster />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
@@ -34,6 +34,12 @@ export function useLogin() {
|
|||||||
const [passwordAuthEnabled, setPasswordAuthEnabled] = useState(true);
|
const [passwordAuthEnabled, setPasswordAuthEnabled] = useState(true);
|
||||||
const [authConfigLoading, setAuthConfigLoading] = useState(true);
|
const [authConfigLoading, setAuthConfigLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthenticated === true) {
|
||||||
|
router.replace("/dashboard");
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, router]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const errorParam = searchParams.get("error");
|
const errorParam = searchParams.get("error");
|
||||||
const messageParam = searchParams.get("message");
|
const messageParam = searchParams.get("message");
|
||||||
|
@@ -17,7 +17,7 @@ export default function LoginPage() {
|
|||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { firstAccess } = useAppInfo();
|
const { firstAccess } = useAppInfo();
|
||||||
|
|
||||||
if (login.isAuthenticated === null) {
|
if (login.isAuthenticated === null || login.isAuthenticated === true) {
|
||||||
return <LoadingScreen />;
|
return <LoadingScreen />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
55
apps/web/src/components/auth/redirect-handler.tsx
Normal file
55
apps/web/src/components/auth/redirect-handler.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import { LoadingScreen } from "@/components/layout/loading-screen";
|
||||||
|
import { useAuth } from "@/contexts/auth-context";
|
||||||
|
|
||||||
|
interface RedirectHandlerProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicPaths = ["/login", "/forgot-password", "/reset-password", "/auth/callback", "/auth/oidc/callback"];
|
||||||
|
const homePaths = ["/"];
|
||||||
|
|
||||||
|
export function RedirectHandler({ children }: RedirectHandlerProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthenticated === true) {
|
||||||
|
if (publicPaths.some((path) => pathname.startsWith(path)) || homePaths.includes(pathname)) {
|
||||||
|
router.replace("/dashboard");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (isAuthenticated === false) {
|
||||||
|
if (!publicPaths.some((path) => pathname.startsWith(path)) && !homePaths.includes(pathname)) {
|
||||||
|
router.replace("/login");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, pathname, router]);
|
||||||
|
|
||||||
|
if (isAuthenticated === null) {
|
||||||
|
return <LoadingScreen />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isAuthenticated === true &&
|
||||||
|
(publicPaths.some((path) => pathname.startsWith(path)) || homePaths.includes(pathname))
|
||||||
|
) {
|
||||||
|
return <LoadingScreen />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isAuthenticated === false &&
|
||||||
|
!publicPaths.some((path) => pathname.startsWith(path)) &&
|
||||||
|
!homePaths.includes(pathname)
|
||||||
|
) {
|
||||||
|
return <LoadingScreen />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
@@ -39,11 +39,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
try {
|
try {
|
||||||
const appInfoResponse = await getAppInfo();
|
const appInfoResponse = await getAppInfo();
|
||||||
const appInfo = appInfoResponse.data;
|
const appInfo = appInfoResponse.data;
|
||||||
|
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
if (appInfo.firstUserAccess) {
|
if (appInfo.firstUserAccess) {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setIsAdmin(false);
|
setIsAdmin(false);
|
||||||
@@ -52,8 +56,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await getCurrentUser();
|
const response = await getCurrentUser();
|
||||||
|
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
if (!response?.data?.user) {
|
if (!response?.data?.user) {
|
||||||
throw new Error("No user data");
|
setUser(null);
|
||||||
|
setIsAdmin(false);
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isAdmin, ...userData } = response.data.user;
|
const { isAdmin, ...userData } = response.data.user;
|
||||||
@@ -62,6 +72,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
setIsAdmin(isAdmin);
|
setIsAdmin(isAdmin);
|
||||||
setIsAuthenticated(true);
|
setIsAuthenticated(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
console.error(err);
|
console.error(err);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setIsAdmin(false);
|
setIsAdmin(false);
|
||||||
@@ -70,6 +82,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
checkAuth();
|
checkAuth();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
Reference in New Issue
Block a user