Skip to content

Commit

Permalink
feat(vue): Auth
Browse files Browse the repository at this point in the history
  • Loading branch information
donmccurdy committed Sep 5, 2024
1 parent 6a35943 commit 15a0e08
Show file tree
Hide file tree
Showing 17 changed files with 237 additions and 54 deletions.
11 changes: 0 additions & 11 deletions packages/create-react/src/components/common/LazyLoadComponent.tsx

This file was deleted.

11 changes: 0 additions & 11 deletions packages/create-react/src/components/common/LazyLoadRoute.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ export default function ProtectedRoute({
const { isAuthenticated, isLoading } = useAuth0();
const { oauth, accessToken } = useContext(AppContext);

if (!oauth.enabled) {
return children;
}

if (!isLoading && !isAuthenticated && !accessToken) {
if (oauth.enabled && !isLoading && !isAuthenticated && !accessToken) {
return <Navigate to={RoutePath.LOGIN} />;
}

return accessToken ? children : null;
return !oauth.enabled || accessToken ? children : null;
}
2 changes: 1 addition & 1 deletion packages/create-react/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const Login = lazy(() => import('./components/views/Login'));
// eslint-disable-next-line react-refresh/only-export-components
const Logout = lazy(() => import('./components/views/Logout'));

export const RoutePath: Record<string, string> = {
export const RoutePath = {
DEFAULT: '/',
US_POPULATION: '/usa-population',

Expand Down
5 changes: 5 additions & 0 deletions packages/create-vue/.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# Populated by @carto/create-common.
VITE_APP_TITLE="$title"
VITE_CARTO_ACCESS_TOKEN="$accessToken"

VITE_CARTO_AUTH_ENABLED="$authEnabled"
VITE_CARTO_AUTH_DOMAIN="$authDomain"
VITE_CARTO_AUTH_CLIENT_ID="$authClientID"
6 changes: 2 additions & 4 deletions packages/create-vue/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<script setup lang="ts">
import AppLayout from './components/common/AppLayout.vue';
</script>
<script setup lang="ts"></script>

<template>
<AppLayout></AppLayout>
<RouterView />
</template>
8 changes: 6 additions & 2 deletions packages/create-vue/src/components/common/AppLayout.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { context } from '../../context';
import { routes } from '../../routes';
import { routes, RoutePath, NAV_ROUTES } from '../../routes';
</script>

<template>
Expand All @@ -14,14 +14,18 @@ import { routes } from '../../routes';
<span class="app-bar-text body1 strong">{{ context.title }}</span>
<nav v-if="routes.length > 1" class="app-bar-nav">
<RouterLink
v-for="route in routes"
v-for="route in NAV_ROUTES"
:to="route.path"
:key="route.path"
class="body2 strong"
:activeClass="'active'"
>{{ route.text }}</RouterLink
>
</nav>
<span className="flex-space" />
<RouterLink :to="RoutePath.LOGOUT" class="body2 strong"
>Sign out</RouterLink
>
</header>
<div class="container">
<RouterView />
Expand Down
30 changes: 30 additions & 0 deletions packages/create-vue/src/components/common/ProtectedRoute.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { effect } from 'vue';
import AppLayout from './AppLayout.vue';
import { useAuth0 } from '@auth0/auth0-vue';
import { context } from '../../context';
import { router, RoutePath } from '../../routes';
import { useAuth } from '../../hooks/useAuth';
useAuth();
const { isAuthenticated, isLoading } = useAuth0();
effect(() => {
if (
context.oauth.enabled &&
!context.accessToken &&
!isLoading.value &&
!isAuthenticated.value
) {
router.replace(RoutePath.LOGIN);
}
});
</script>

<template>
<template v-if="!context.oauth.enabled || context.accessToken">
<AppLayout>
<slot />
</AppLayout>
</template>
</template>
4 changes: 3 additions & 1 deletion packages/create-vue/src/components/views/Default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Legend from '../common/Legend.vue';
import Card from '../common/Card.vue';
import FormulaWidget from '../widgets/FormulaWidget.vue';
import CategoryWidget from '../widgets/CategoryWidget.vue';
import { context } from '../../context';
const MAP_STYLE =
'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json';
Expand Down Expand Up @@ -47,7 +48,8 @@ const onFiltersChange = (_filters: Record<string, Filter>) => {
const data = computed(() =>
vectorQuerySource({
accessToken: import.meta.env.VITE_CARTO_ACCESS_TOKEN,
accessToken: context.accessToken,
apiBaseUrl: context.apiBaseUrl,
connectionName: 'carto_dw',
sqlQuery:
'SELECT * FROM `carto-demo-data.demo_tables.cell_towers_worldwide`',
Expand Down
22 changes: 22 additions & 0 deletions packages/create-vue/src/components/views/Login.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
import { useAuth0 } from '@auth0/auth0-vue';
import { context } from '../../context';
const { loginWithRedirect } = useAuth0();
</script>

<template>
<main class="login">
<img
v-if="context.logo"
class="login-logo"
:src="context.logo.src"
:alt="context.logo.alt"
/>
<h1 class="title">{{ context.title }}</h1>
<p class="subtitle">Discover the power of developing with React</p>
<button class="body1" @click="() => loginWithRedirect()">
Login with CARTO
</button>
<p class="caption">Use your CARTO credentials</p>
</main>
</template>
9 changes: 9 additions & 0 deletions packages/create-vue/src/components/views/Logout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script setup lang="ts">
import { useAuth0 } from '@auth0/auth0-vue';
const { logout } = useAuth0();
logout();
</script>

<template>
<p>Logging out…</p>
</template>
3 changes: 3 additions & 0 deletions packages/create-vue/src/components/views/NotFound.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<h1>Not Found</h1>
</template>
4 changes: 3 additions & 1 deletion packages/create-vue/src/components/views/Secondary.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { h3TableSource } from '@carto/api-client';
import Layers from '../common/Layers.vue';
import Legend from '../common/Legend.vue';
import Card from '../common/Card.vue';
import { context } from '../../context';
const MAP_STYLE =
'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json';
Expand All @@ -38,7 +39,8 @@ const POP_COLORS: AccessorFunction<unknown, Color> = colorContinuous({
const data = computed(() =>
h3TableSource({
accessToken: import.meta.env.VITE_CARTO_ACCESS_TOKEN,
accessToken: context.accessToken,
apiBaseUrl: context.apiBaseUrl,
connectionName: 'carto_dw',
tableName:
'carto-demo-data.demo_tables.derived_spatialfeatures_usa_h3res8_v1_yearly_v2',
Expand Down
43 changes: 33 additions & 10 deletions packages/create-vue/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { reactive } from 'vue';
import cartoLogo from '/carto.svg';

export interface AppContext {
Expand All @@ -6,26 +7,48 @@ export interface AppContext {
src: string;
alt: string;
};
credentials: {
accessToken: string;
apiVersion?: string;
apiBaseUrl?: string;
};
accessToken: string;
apiVersion?: string;
apiBaseUrl?: string;
googleApiKey?: string;
googleMapId?: string;
accountsUrl?: string;
oauth: {
enabled: boolean;
domain: string;
clientId?: string;
organizationId?: string;
namespace: string;
scopes: string[];
audience: string;
authorizeEndPoint?: string;
};
}

export const DEFAULT_APP_CONTEXT: AppContext = {
title: '$title',
title: import.meta.env.VITE_APP_TITLE,
logo: {
src: cartoLogo,
alt: 'CARTO logo',
},
credentials: {
accessToken: import.meta.env.VITE_CARTO_ACCESS_TOKEN,
},
accessToken: import.meta.env.VITE_CARTO_ACCESS_TOKEN,
apiBaseUrl: 'https://gcp-us-east1.api.carto.com',
accountsUrl: 'http://app.carto.com/',
oauth: {
enabled: import.meta.env.VITE_CARTO_AUTH_ENABLED === 'true',
domain: 'auth.carto.com',
clientId: import.meta.env.VITE_CARTO_AUTH_CLIENT_ID,
organizationId: import.meta.env.VITE_CARTO_AUTH_ORGANIZATION_ID, // Required for SSO.
namespace: 'http://app.carto.com/',
scopes: [
'read:current_user',
'read:connections',
'read:maps',
'read:account',
],
audience: 'carto-cloud-native-api',
authorizeEndPoint: 'https://carto.com/oauth2/authorize', // Only valid if keeping https://localhost:3000/oauthCallback
},
};

export const context: AppContext = { ...DEFAULT_APP_CONTEXT };
export const context = reactive<AppContext>({ ...DEFAULT_APP_CONTEXT });
47 changes: 47 additions & 0 deletions packages/create-vue/src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useAuth0 } from '@auth0/auth0-vue';
import { useRoute } from 'vue-router';
import { context } from '../context';
import { computed, effect } from 'vue';

const FORCE_LOGIN_PARAM = 'forceLogin';

export function useAuth() {
const route = useRoute();
const {
isAuthenticated,
getAccessTokenSilently,
user,
loginWithRedirect,
isLoading,
} = useAuth0();

const organizationId = context.oauth.organizationId || '';
const namespace = context.oauth.namespace;

const userMetadata = computed(() => {
return user.value?.[`${namespace}user_metadata`];
});

const redirectAccountUri = computed(() => {
return organizationId
? `${context.accountsUrl}sso/${organizationId}`
: context.accountsUrl;
});

effect(async () => {
if (route.query[FORCE_LOGIN_PARAM]) {
// if FORCE_LOGIN_PARAM is set a relogin is required to refresh userMetadata
loginWithRedirect();
} else if (isAuthenticated.value && userMetadata.value) {
context.accessToken = await getAccessTokenSilently();
} else if (isAuthenticated.value && !isLoading.value) {
// No organizations associated with the user, we need to redirect to app.carto.com to complete the signup process
const searchParams = new URLSearchParams({
redirectUri: `${window.location.origin}?${FORCE_LOGIN_PARAM}=true`,
});
window.location.href = `${redirectAccountUri.value}?${searchParams}`;
}
});

return;
}
28 changes: 26 additions & 2 deletions packages/create-vue/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import { createApp } from 'vue';
import { createApp, Plugin } from 'vue';
import '@carto/create-common/style.css';
import App from './App.vue';
import { router } from './routes';
import { context } from './context';
import { createAuth0 } from '@auth0/auth0-vue';

createApp(App).use(router).mount('#root');
const app = createApp(App);

app.use(router);

if (context.oauth.enabled) {
app.use(
createAuth0({
domain: context.oauth.domain,
clientId: context.oauth.clientId!,
cacheLocation: 'localstorage',
authorizationParams: {
audience: context.oauth.audience,
organization: context.oauth.organizationId,
redirect_uri: window.location.origin,
scope: context.oauth.scopes.join(' '),
},
}) as unknown as Plugin<[]>,
);
}

app.mount('#root');

document.title = context.title;
Loading

0 comments on commit 15a0e08

Please sign in to comment.