Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions assets/overlay.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* Request successfully submitted modal */
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75);
z-index: 1040;
}

.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 20px;
z-index: 1050;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
animation: fadeIn 0.5s ease;
}

@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
43 changes: 43 additions & 0 deletions components/Auth0Login.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts" setup>
import { useUserSession, onMounted } from "#imports";
import LanguagePicker from "@/components/shared/LanguagePicker.vue";

interface Props {
errorMessage: string;
}
const props = defineProps<Props>();
const { loggedIn } = useUserSession();
const loginWithAuth0 = () => {
window.location.href = "/api/auth/auth0";
};
onMounted(() => {
const redirectUrl = sessionStorage.getItem("redirect_url");
if (redirectUrl && loggedIn.value) {
sessionStorage.removeItem("redirect_url");
window.location.href = redirectUrl;
}
});
</script>

<template>
<div class="container relative">
<div
class="absolute top-0 right-0 flex justify-end space-x-4 mt-4 mr-4 mb-4"
>
<LanguagePicker />
</div>
<div class="flex flex-col items-center justify-center h-screen">
<p class="italic">{{ $t("authMessage") }}.</p>
<button
data-testid="login-button"
class="px-4 py-2 mt-4 mb-4 bg-blue-500 text-white rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-opacity-50"
@click="loginWithAuth0"
>
{{ $t("loginButton") }}
</button>
<p v-if="props.errorMessage" class="text-red-500 text-xs italic">
{{ $t("yourAccessIsPending") }}
</p>
</div>
</div>
</template>
1 change: 1 addition & 0 deletions components/MapDashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useI18n } from "vue-i18n";

import QRCode from "qrcode.vue";
import MiniMap from "@/components/MapDashboard/MiniMap.vue";
import LanguagePicker from "@/components/shared/LanguagePicker.vue";

import { copyLink } from "@/utils";

Expand Down
68 changes: 68 additions & 0 deletions components/shared/LanguagePicker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";

const { locale, locales, setLocale } = useI18n();

// Populate available locales from i18n plugin
const availableLocales = computed(() => locales.value);
const currentLocaleName = computed(() => {
const currentLocale = locales.value.find(
(lang) => lang.code === locale.value,
);
return currentLocale?.name || "";
});

const dropdownOpen = ref(false);

const changeLocale = (locale: { code: string }): void => {
setLocale(locale.code as "en" | "es" | "pt" | "nl");
dropdownOpen.value = false;
};
</script>

<template>
<div class="relative inline-block text-left">
<div>
<button
class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none"
@click="dropdownOpen = !dropdownOpen"
>
{{ currentLocaleName }}
<svg
class="-mr-1 ml-2 h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
<div
v-if="dropdownOpen"
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50"
>
<div class="py-1">
<a
v-for="lang in availableLocales"
:key="lang.code"
href="#"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
@click="
($event.preventDefault(),
$event.stopPropagation(),
changeLocale(lang))
"
>
{{ lang.name }}
</a>
</div>
</div>
</div>
</template>
35 changes: 35 additions & 0 deletions composables/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ref } from "vue";
import { useRouter, useLocalePath } from "#imports";

export const useAuth = (loggedIn: { value: boolean }) => {
let errorMessage;
const redirectPath = ref("");

const router = useRouter();
const localePath = useLocalePath();

const redirect = router.currentRoute.value.query.redirect;
redirectPath.value = redirect
? decodeURIComponent(redirect as string)
: localePath("/");

const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");

if (code) {
window.location.href = `/api/auth/auth0?code=${code}`;
}

const error = urlParams.get("error");
const errorDescription = urlParams.get("error_description");

if (error === "access_denied") {
errorMessage = decodeURIComponent(errorDescription || "");
}

if (loggedIn.value) {
router.push(redirectPath.value);
}

return errorMessage;
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
24 changes: 24 additions & 0 deletions middleware/oauth.global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { defineNuxtRouteMiddleware, useRuntimeConfig } from "#imports";

// Following example: https://github.com/atinux/atidone/blob/main/app/middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to) => {
const { loggedIn } = useUserSession();
const {
public: { authStrategy },
} = useRuntimeConfig();
const router = useRouter();

if (import.meta.client) {
if (to.path.includes("/login")) {
const back = window.history.state.back;
const current = window.history.state.current;

if (!current.includes("/login") && !back) {
sessionStorage.setItem("redirect_url", current);
}
}
}
if (authStrategy === "auth0" && !loggedIn.value && to.path !== "/login") {
return router.push("/login");
}
});
21 changes: 17 additions & 4 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ export default defineNuxtConfig({
devtools: { enabled: true },

nitro: {
plugins: ["@/server/index.ts"],
plugins: ["@/server/index.ts", "@/server/plugins/apiAuth.ts"],
routeRules: {
"/api/**": {
cors: true,
},
},
},

css: ["mapbox-gl/dist/mapbox-gl.css"],
css: ["mapbox-gl/dist/mapbox-gl.css", "~/assets/overlay.css"],

modules: [
"gc-shared-resources",
"nuxt-auth-utils",
"@nuxt/eslint",
"@nuxtjs/i18n",
Expand All @@ -49,7 +53,6 @@ export default defineNuxtConfig({
alwaysRedirect: true,
redirectOn: "all",
},
langDir: "lang/",
strategy: "no_prefix",
skipSettingLocaleOnNavigate: true, // persists locale when route changes
},
Expand All @@ -66,6 +69,16 @@ export default defineNuxtConfig({
dbSsl: "true",
dbTable: "",
port: "8080",
// Session secret for nuxt-auth-utils
sessionSecret: "your-session-secret-key-change-in-production",
// OAuth configuration for nuxt-auth-utils
oauth: {
auth0: {
clientId: "",
clientSecret: "",
domain: "",
},
},
public: {
appApiKey: "",
authStrategy: "none",
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"dependencies": {
"@azure/storage-queue": "^12.22.0",
"@mapbox/mapbox-gl-draw": "^1.4.3",
"gc-shared-resources": "latest",
"mapbox-gl": "^3.4.0",
"mapbox-gl-draw-rectangle-mode": "^1.0.4",
"nuxt": "^3.12.2",
Expand All @@ -35,7 +34,7 @@
"devDependencies": {
"@nuxt/eslint": "^1.0.1",
"@nuxt/test-utils": "^3.14.1",
"@nuxtjs/i18n": "^8.3.1",
"@nuxtjs/i18n": "^9.5.6",
"@types/mapbox-gl": "^3.1.0",
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
"@types/pg": "^8.11.11",
Expand Down
11 changes: 6 additions & 5 deletions pages/login.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
<script lang="ts" setup>
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { useAuth } from "~/composables/useAuth";

const { loggedIn } = useUserSession();
const errorMessage = ref("");

onMounted(() => {
const authResult = useAuth(loggedIn);
errorMessage.value = authResult !== undefined ? authResult : "";
const authError = useAuth(loggedIn);
errorMessage.value = authError || "";
});

const { t } = useI18n();
useHead({
title: "MapPacker: " + t("login"),
title: "Map Packer: " + t("login"),
});
</script>

<template>
<Auth0Login v-if="loggedIn === false" :error-message="errorMessage" />
<Auth0Login :error-message="errorMessage" />
</template>
Loading