mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-28 11:36:53 -05:00
feat: add safe redirects from login pages
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
PN_WEBSITE_PUBLIC_BASE_URL=http://localhost:3000
|
||||
PN_WEBSITE_PUBLIC_CDN_BASE_URL=http://pretendo.localhost:3902
|
||||
PN_WEBSITE_PUBLIC_COOKIE_SECURE=false
|
||||
PN_WEBSITE_PUBLIC_REDIRECT_HOSTS=localhost:3000
|
||||
|
||||
# Optional - Authentication
|
||||
PN_WEBSITE_GRPC_HOST=localhost:8123
|
||||
|
||||
@@ -65,6 +65,7 @@ export default defineNuxtConfig({
|
||||
public: {
|
||||
baseUrl: 'https://pretendo.network',
|
||||
cdnBaseUrl: 'https://r2-cdn.pretendo.cc',
|
||||
redirectHosts: 'pretendo.network',
|
||||
hcaptchaSiteKey: '',
|
||||
cookieSecure: true
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Popover } from 'reka-ui/namespaced';
|
||||
const { locales, setLocale } = useI18n();
|
||||
|
||||
const me = useMeStore();
|
||||
const authStore = useAuthStore();
|
||||
const authUtils = useAuthUtils();
|
||||
const user = computed(() => me.user);
|
||||
|
||||
const openDropdown = ref<boolean | string>(false);
|
||||
@@ -522,7 +522,7 @@ onMounted(() => {
|
||||
</a>
|
||||
<button
|
||||
class="button logout"
|
||||
@click="authStore.logout()"
|
||||
@click="authUtils.logout()"
|
||||
>
|
||||
{{ $t("nav.accountWidget.logout") }}
|
||||
</button>
|
||||
|
||||
62
src/composables/useAuthUtils.ts
Normal file
62
src/composables/useAuthUtils.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
export function getSafeRedirectUrl(input: string | null, baseUrl: string, allowedHosts: string[] = []): { url: string; external: boolean } | null {
|
||||
const parsedBaseUrl = new URL(baseUrl);
|
||||
if (!input) {
|
||||
return null; // No or empty input
|
||||
}
|
||||
|
||||
let url: URL | null = null;
|
||||
try {
|
||||
url = new URL(input, parsedBaseUrl);
|
||||
} catch {
|
||||
return null; // Invalid URL input
|
||||
}
|
||||
|
||||
const isAllowedHost = allowedHosts.includes(url.host);
|
||||
if (!isAllowedHost) {
|
||||
return null; // Not in the allowed hosts
|
||||
}
|
||||
|
||||
// If on the same host as the website, only return the path. For internal redirects
|
||||
if (url.host === parsedBaseUrl.host) {
|
||||
return {
|
||||
url: url.pathname + url.search,
|
||||
external: false
|
||||
};
|
||||
}
|
||||
return {
|
||||
url: url.toString(),
|
||||
external: true
|
||||
};
|
||||
}
|
||||
|
||||
export function useAuthUtils() {
|
||||
const authStore = useAuthStore();
|
||||
const route = useRoute();
|
||||
const config = useRuntimeConfig();
|
||||
const allowedRedirectHosts = computed(() => config.public.redirectHosts.split(' ').map(v => v.trim()).filter(v => v.length > 0));
|
||||
|
||||
return {
|
||||
async safelyRedirectAfterLogin(inputUrl: string | null) {
|
||||
const redirectUrl = getSafeRedirectUrl(inputUrl, config.public.baseUrl, allowedRedirectHosts.value);
|
||||
if (!redirectUrl) {
|
||||
await navigateTo('/account');
|
||||
return;
|
||||
}
|
||||
await navigateTo(redirectUrl.url, {
|
||||
external: redirectUrl.external
|
||||
});
|
||||
},
|
||||
async redirectToLogin() {
|
||||
await navigateTo({
|
||||
path: '/account/login',
|
||||
query: {
|
||||
redirect: route.fullPath
|
||||
}
|
||||
});
|
||||
},
|
||||
async logout() {
|
||||
authStore.logout();
|
||||
await navigateTo('/'); // Back to homepage
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,8 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const auth = useAuthStore();
|
||||
const redirect = computed(() => route.query.redirect);
|
||||
const registerURI = computed(() => `/account/register${redirect.value ? `?redirect=${redirect.value}` : ''}`);
|
||||
const authUtils = useAuthUtils();
|
||||
const redirect = computed(() => route.query.redirect?.toString() ?? null);
|
||||
const registerURI = computed(() => {
|
||||
if (redirect.value) {
|
||||
const params = new URLSearchParams({
|
||||
redirect: redirect.value
|
||||
});
|
||||
return `/account/register?${params}`;
|
||||
}
|
||||
return `/account/register`;
|
||||
});
|
||||
|
||||
const loginForm = reactive({ username: '', password: '' });
|
||||
const errorMessage = ref<string | null>();
|
||||
@@ -20,12 +29,7 @@ async function loginSubmission() {
|
||||
accessToken: res.accessToken,
|
||||
refreshToken: res.refreshToken
|
||||
});
|
||||
|
||||
if (typeof redirect.value === 'string') {
|
||||
await navigateTo(redirect.value, { external: true });
|
||||
} else {
|
||||
await navigateTo('/account');
|
||||
}
|
||||
await authUtils.safelyRedirectAfterLogin(redirect.value);
|
||||
} catch (error: unknown) {
|
||||
const err = getApiError(error);
|
||||
errorMessage.value = err.code;
|
||||
|
||||
@@ -4,8 +4,17 @@ import type { ApiAuthRegisterRequest } from '~~/shared/api-types';
|
||||
|
||||
const route = useRoute();
|
||||
const auth = useAuthStore();
|
||||
const redirect = computed(() => route.query.redirect);
|
||||
const loginURI = computed(() => `/account/login${redirect.value ? `?redirect=${redirect.value}` : ''}`);
|
||||
const authUtils = useAuthUtils();
|
||||
const redirect = computed(() => route.query.redirect?.toString() ?? null);
|
||||
const loginURI = computed(() => {
|
||||
if (redirect.value) {
|
||||
const params = new URLSearchParams({
|
||||
redirect: redirect.value
|
||||
});
|
||||
return `/account/login?${params}`;
|
||||
}
|
||||
return `/account/login`;
|
||||
});
|
||||
|
||||
const registerForm = reactive({ email: '', username: '', mii_name: '', password: '', password_confirm: '' });
|
||||
|
||||
@@ -30,12 +39,7 @@ async function registerSubmission() {
|
||||
accessToken: res.accessToken,
|
||||
refreshToken: res.refreshToken
|
||||
});
|
||||
|
||||
if (typeof redirect.value === 'string') {
|
||||
await navigateTo(redirect.value, { external: true });
|
||||
} else {
|
||||
await navigateTo('/account');
|
||||
}
|
||||
await authUtils.safelyRedirectAfterLogin(redirect.value);
|
||||
} catch (error: unknown) {
|
||||
if (error === 'challenge-closed') {
|
||||
// Thrown if the captcha is closed, can be safely ignored
|
||||
|
||||
2
src/plugins/types.d.ts
vendored
2
src/plugins/types.d.ts
vendored
@@ -42,6 +42,8 @@ declare module 'nuxt/schema' {
|
||||
|
||||
interface PublicRuntimeConfig {
|
||||
baseUrl: string;
|
||||
cdnBaseUrl: string;
|
||||
redirectHosts: string;
|
||||
cookieSecure: boolean;
|
||||
hcaptchaSiteKey: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user