diff --git a/frontend/src/lib/i18n/i18n.ts b/frontend/src/lib/i18n/i18n.ts
index eb1b809..9c50f6f 100644
--- a/frontend/src/lib/i18n/i18n.ts
+++ b/frontend/src/lib/i18n/i18n.ts
@@ -5,6 +5,20 @@ import ChainedBackend from "i18next-chained-backend";
import resourcesToBackend from "i18next-resources-to-backend";
import HttpBackend from "i18next-http-backend";
+const backends = [
+ HttpBackend,
+ resourcesToBackend(
+ (language: string) => import(`./locales/${language}.json`),
+ ),
+]
+
+const backendOptions = [
+ {
+ loadPath: "https://cdn.tinyauth.app/i18n/v1/{{lng}}.json",
+ },
+ {}
+]
+
i18n
.use(ChainedBackend)
.use(LanguageDetector)
@@ -20,17 +34,8 @@ i18n
load: "currentOnly",
backend: {
- backends: [
- HttpBackend,
- resourcesToBackend(
- (language: string) => import(`./locales/${language}.json`),
- ),
- ],
- backendOptions: [
- {
- loadPath: "https://cdn.tinyauth.app/i18n/v1/{{lng}}.json",
- },
- ],
+ backends: import.meta.env.MODE !== "development" ? backends : backends.reverse(),
+ backendOptions: import.meta.env.MODE !== "development" ? backendOptions : backendOptions.reverse()
},
});
diff --git a/frontend/src/lib/i18n/locales/en-US.json b/frontend/src/lib/i18n/locales/en-US.json
index b99ebf2..b31e2ee 100644
--- a/frontend/src/lib/i18n/locales/en-US.json
+++ b/frontend/src/lib/i18n/locales/en-US.json
@@ -42,5 +42,8 @@
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource {{resource}}.",
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
- "unauthorizedButton": "Try again"
+ "unauthorizedButton": "Try again",
+ "untrustedRedirectTitle": "Untrusted redirect",
+ "untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain ({{domain}}). Are you sure you want to continue?",
+ "cancelTitle": "Cancel"
}
\ No newline at end of file
diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json
index b99ebf2..b31e2ee 100644
--- a/frontend/src/lib/i18n/locales/en.json
+++ b/frontend/src/lib/i18n/locales/en.json
@@ -42,5 +42,8 @@
"unauthorizedTitle": "Unauthorized",
"unauthorizedResourceSubtitle": "The user with username {{username}} is not authorized to access the resource {{resource}}.",
"unaothorizedLoginSubtitle": "The user with username {{username}} is not authorized to login.",
- "unauthorizedButton": "Try again"
+ "unauthorizedButton": "Try again",
+ "untrustedRedirectTitle": "Untrusted redirect",
+ "untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain ({{domain}}). Are you sure you want to continue?",
+ "cancelTitle": "Cancel"
}
\ No newline at end of file
diff --git a/frontend/src/pages/continue-page.tsx b/frontend/src/pages/continue-page.tsx
index 41582b3..8061282 100644
--- a/frontend/src/pages/continue-page.tsx
+++ b/frontend/src/pages/continue-page.tsx
@@ -4,7 +4,7 @@ import { Navigate } from "react-router";
import { useUserContext } from "../context/user-context";
import { Layout } from "../components/layouts/layout";
import { ReactNode } from "react";
-import { isQueryValid } from "../utils/utils";
+import { escapeRegex, isQueryValid } from "../utils/utils";
import { useAppContext } from "../context/app-context";
import { Trans, useTranslation } from "react-i18next";
@@ -14,7 +14,7 @@ export const ContinuePage = () => {
const redirectUri = params.get("redirect_uri") ?? "";
const { isLoggedIn } = useUserContext();
- const { disableContinue } = useAppContext();
+ const { disableContinue, domain } = useAppContext();
const { t } = useTranslation();
if (!isLoggedIn) {
@@ -51,6 +51,30 @@ export const ContinuePage = () => {
);
}
+ const regex = new RegExp(`^.*${escapeRegex(domain)}$`)
+
+ if (!regex.test(uri.hostname)) {
+ return (
+
+
+ {t("untrustedRedirectTitle")}
+
+ }}
+ values={{ domain: domain }}
+ />
+
+
+
+ )
+ }
+
if (disableContinue) {
window.location.href = redirectUri;
return (
@@ -79,6 +103,9 @@ export const ContinuePage = () => {
+
);
}
diff --git a/frontend/src/schemas/app-context-schema.ts b/frontend/src/schemas/app-context-schema.ts
index 987fff9..3738c3f 100644
--- a/frontend/src/schemas/app-context-schema.ts
+++ b/frontend/src/schemas/app-context-schema.ts
@@ -5,6 +5,7 @@ export const appContextSchema = z.object({
disableContinue: z.boolean(),
title: z.string(),
genericName: z.string(),
+ domain: z.string(),
});
export type AppContextSchemaType = z.infer;
diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts
index 038230e..bef72fc 100644
--- a/frontend/src/utils/utils.ts
+++ b/frontend/src/utils/utils.ts
@@ -1,2 +1,3 @@
export const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
export const isQueryValid = (value: string) => value.trim() !== "" && value !== "null";
+export const escapeRegex = (value: string) => value.replace(/[-\/\\^$.*+?()[\]{}|]/g, "\\$&");
\ No newline at end of file
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index 99739f9..9e48764 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -446,6 +446,7 @@ func (h *Handlers) AppHandler(c *gin.Context) {
DisableContinue: h.Config.DisableContinue,
Title: h.Config.Title,
GenericName: h.Config.GenericName,
+ Domain: h.Config.Domain,
}
// Return app context
diff --git a/internal/types/api.go b/internal/types/api.go
index 2d8df9d..e9307a6 100644
--- a/internal/types/api.go
+++ b/internal/types/api.go
@@ -46,6 +46,7 @@ type AppContext struct {
DisableContinue bool `json:"disableContinue"`
Title string `json:"title"`
GenericName string `json:"genericName"`
+ Domain string `json:"domain"`
}
// Totp request is the request for the totp endpoint