feat: implement validateRedirect utility for open redirect protection
This commit is contained in:
@@ -15,6 +15,7 @@ export interface LoggedInUser extends RecordModel {
|
|||||||
const user = ref<LoggedInUser | null>(null);
|
const user = ref<LoggedInUser | null>(null);
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const error = ref<Error | null>(null);
|
const error = ref<Error | null>(null);
|
||||||
|
let isInitialized = false;
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const pb = usePocketbase();
|
const pb = usePocketbase();
|
||||||
@@ -22,13 +23,18 @@ export const useAuth = () => {
|
|||||||
|
|
||||||
const userCollection = 'users';
|
const userCollection = 'users';
|
||||||
|
|
||||||
const isAuthenticated = computed<boolean>(() => pb.authStore.isValid && !!user.value);
|
|
||||||
|
|
||||||
const initAuth = async () => {
|
const initAuth = async () => {
|
||||||
user.value = pb.authStore.record as LoggedInUser;
|
user.value = pb.authStore.record as LoggedInUser;
|
||||||
pb.authStore.onChange((_token, model) => (user.value = model as LoggedInUser));
|
pb.authStore.onChange((_token, model) => (user.value = model as LoggedInUser));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!isInitialized) {
|
||||||
|
initAuth();
|
||||||
|
isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAuthenticated = computed<boolean>(() => pb.authStore.isValid && !!user.value);
|
||||||
|
|
||||||
const authProviders = async (): Promise<AuthProviderInfo[]> => {
|
const authProviders = async (): Promise<AuthProviderInfo[]> => {
|
||||||
const authMethods = await pb.collection(userCollection).listAuthMethods();
|
const authMethods = await pb.collection(userCollection).listAuthMethods();
|
||||||
return authMethods.oauth2.enabled ? authMethods.oauth2.providers : [];
|
return authMethods.oauth2.enabled ? authMethods.oauth2.providers : [];
|
||||||
@@ -44,7 +50,9 @@ export const useAuth = () => {
|
|||||||
throw new Error(`${provider} OAuth is not configured`);
|
throw new Error(`${provider} OAuth is not configured`);
|
||||||
}
|
}
|
||||||
const response = await pb.collection(userCollection).authWithOAuth2({ provider });
|
const response = await pb.collection(userCollection).authWithOAuth2({ provider });
|
||||||
|
console.log('Auth response:', response)
|
||||||
user.value = response.record as LoggedInUser;
|
user.value = response.record as LoggedInUser;
|
||||||
|
console.log('User value', user.value)
|
||||||
} catch (pbError) {
|
} catch (pbError) {
|
||||||
error.value = pbError as Error;
|
error.value = pbError as Error;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -64,9 +72,9 @@ export const useAuth = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
pb.authStore.clear();
|
|
||||||
user.value = null;
|
user.value = null;
|
||||||
error.value = null;
|
error.value = null;
|
||||||
|
pb.authStore.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -76,7 +84,6 @@ export const useAuth = () => {
|
|||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
initAuth,
|
|
||||||
refreshAuth,
|
refreshAuth,
|
||||||
handleOAuthCallback,
|
handleOAuthCallback,
|
||||||
authProviders,
|
authProviders,
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ definePageMeta({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const redirectPath = (route.query.redirect as string) || '/dashboard';
|
const redirectPath = validateRedirect(route.query.redirect, '/dashboard');
|
||||||
const { authProviders, error, isAuthenticated } = useAuth();
|
const { authProviders, error, isAuthenticated } = useAuth();
|
||||||
|
|
||||||
const providers = await authProviders();
|
const providers = await authProviders();
|
||||||
|
const redirect = (authenticated: boolean) => {
|
||||||
watch(isAuthenticated, (authenticated) => {
|
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
navigateTo(redirectPath);
|
navigateTo(redirectPath);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
redirect(isAuthenticated.value);
|
||||||
|
watch(isAuthenticated, redirect);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
9
app/utils/validateRedirect.ts
Normal file
9
app/utils/validateRedirect.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const validateRedirect = (redirect: string | unknown, fallback = '/dashboard'): string => {
|
||||||
|
if (typeof redirect !== 'string') {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
if (redirect.startsWith('/') && !redirect.startsWith('//')) {
|
||||||
|
return redirect;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user