mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 12:45:47 +00:00
feat: add trusted URLs
This commit is contained in:
@@ -5,6 +5,20 @@ import ChainedBackend from "i18next-chained-backend";
|
|||||||
import resourcesToBackend from "i18next-resources-to-backend";
|
import resourcesToBackend from "i18next-resources-to-backend";
|
||||||
import HttpBackend from "i18next-http-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
|
i18n
|
||||||
.use(ChainedBackend)
|
.use(ChainedBackend)
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
@@ -20,17 +34,8 @@ i18n
|
|||||||
load: "currentOnly",
|
load: "currentOnly",
|
||||||
|
|
||||||
backend: {
|
backend: {
|
||||||
backends: [
|
backends: import.meta.env.MODE !== "development" ? backends : backends.reverse(),
|
||||||
HttpBackend,
|
backendOptions: import.meta.env.MODE !== "development" ? backendOptions : backendOptions.reverse()
|
||||||
resourcesToBackend(
|
|
||||||
(language: string) => import(`./locales/${language}.json`),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
backendOptions: [
|
|
||||||
{
|
|
||||||
loadPath: "https://cdn.tinyauth.app/i18n/v1/{{lng}}.json",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -42,5 +42,8 @@
|
|||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> 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 (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
|
"cancelTitle": "Cancel"
|
||||||
}
|
}
|
||||||
@@ -42,5 +42,8 @@
|
|||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> 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 (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
|
"cancelTitle": "Cancel"
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import { Navigate } from "react-router";
|
|||||||
import { useUserContext } from "../context/user-context";
|
import { useUserContext } from "../context/user-context";
|
||||||
import { Layout } from "../components/layouts/layout";
|
import { Layout } from "../components/layouts/layout";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { isQueryValid } from "../utils/utils";
|
import { escapeRegex, isQueryValid } from "../utils/utils";
|
||||||
import { useAppContext } from "../context/app-context";
|
import { useAppContext } from "../context/app-context";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ export const ContinuePage = () => {
|
|||||||
const redirectUri = params.get("redirect_uri") ?? "";
|
const redirectUri = params.get("redirect_uri") ?? "";
|
||||||
|
|
||||||
const { isLoggedIn } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
const { disableContinue } = useAppContext();
|
const { disableContinue, domain } = useAppContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
@@ -51,6 +51,30 @@ export const ContinuePage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(`^.*${escapeRegex(domain)}$`)
|
||||||
|
|
||||||
|
if (!regex.test(uri.hostname)) {
|
||||||
|
return (
|
||||||
|
<ContinuePageLayout>
|
||||||
|
<Text size="xl" fw={700}>
|
||||||
|
{t("untrustedRedirectTitle")}
|
||||||
|
</Text>
|
||||||
|
<Trans
|
||||||
|
i18nKey="untrustedRedirectSubtitle"
|
||||||
|
t={t}
|
||||||
|
components={{ Code: <Code /> }}
|
||||||
|
values={{ domain: domain }}
|
||||||
|
/>
|
||||||
|
<Button fullWidth mt="xl" color="red" onClick={redirect}>
|
||||||
|
{t('continueTitle')}
|
||||||
|
</Button>
|
||||||
|
<Button fullWidth mt="sm" color="gray" onClick={() => window.location.href = "/"}>
|
||||||
|
{t('cancelTitle')}
|
||||||
|
</Button>
|
||||||
|
</ContinuePageLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (disableContinue) {
|
if (disableContinue) {
|
||||||
window.location.href = redirectUri;
|
window.location.href = redirectUri;
|
||||||
return (
|
return (
|
||||||
@@ -79,6 +103,9 @@ export const ContinuePage = () => {
|
|||||||
<Button fullWidth mt="xl" color="yellow" onClick={redirect}>
|
<Button fullWidth mt="xl" color="yellow" onClick={redirect}>
|
||||||
{t("continueTitle")}
|
{t("continueTitle")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button fullWidth mt="sm" color="gray" onClick={() => window.location.href = "/"}>
|
||||||
|
{t('cancelTitle')}
|
||||||
|
</Button>
|
||||||
</ContinuePageLayout>
|
</ContinuePageLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const appContextSchema = z.object({
|
|||||||
disableContinue: z.boolean(),
|
disableContinue: z.boolean(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
genericName: z.string(),
|
genericName: z.string(),
|
||||||
|
domain: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppContextSchemaType = z.infer<typeof appContextSchema>;
|
export type AppContextSchemaType = z.infer<typeof appContextSchema>;
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
export const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
|
||||||
export const isQueryValid = (value: string) => value.trim() !== "" && value !== "null";
|
export const isQueryValid = (value: string) => value.trim() !== "" && value !== "null";
|
||||||
|
export const escapeRegex = (value: string) => value.replace(/[-\/\\^$.*+?()[\]{}|]/g, "\\$&");
|
||||||
@@ -446,6 +446,7 @@ func (h *Handlers) AppHandler(c *gin.Context) {
|
|||||||
DisableContinue: h.Config.DisableContinue,
|
DisableContinue: h.Config.DisableContinue,
|
||||||
Title: h.Config.Title,
|
Title: h.Config.Title,
|
||||||
GenericName: h.Config.GenericName,
|
GenericName: h.Config.GenericName,
|
||||||
|
Domain: h.Config.Domain,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return app context
|
// Return app context
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ type AppContext struct {
|
|||||||
DisableContinue bool `json:"disableContinue"`
|
DisableContinue bool `json:"disableContinue"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
GenericName string `json:"genericName"`
|
GenericName string `json:"genericName"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Totp request is the request for the totp endpoint
|
// Totp request is the request for the totp endpoint
|
||||||
|
|||||||
Reference in New Issue
Block a user