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
4 changes: 0 additions & 4 deletions .github/instructions/nuxt.instructions.md

This file was deleted.

8 changes: 8 additions & 0 deletions .github/instructions/project.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
applyTo: '**'
---
Do not use abbreviations in naming, except for instances where it would be weird not to abbreviate.
Prefer descriptive syntax for naming and only add comments where additional context is necessary.

Nuxt auto-imports are active, so there is no need to import Nuxt components, composables and Vue.js APIs.
Code formatting is done using Prettier.
23 changes: 12 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ FROM base-image AS prepare

COPY ./pnpm-lock.yaml ./package.json ./

## pnpm patches
# COPY ./patches ./patches
# pnpm patches
COPY ./patches ./patches

RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm fetch
Expand Down Expand Up @@ -130,20 +130,20 @@ RUN corepack enable \

FROM test-e2e-base-image AS test-e2e_development

ARG UNAME=e2e
ARG UID=1000
ARG GID=1000
ARG USER_NAME=e2e
ARG USER_ID=1000
ARG GROUP_ID=1000

ENV NODE_ENV=development

COPY ./docker-entrypoint.sh /usr/local/bin/

RUN groupadd -g $GID -o $UNAME \
&& useradd -m -l -u $UID -g $GID -o -s /bin/bash $UNAME \
RUN groupadd -g $GROUP_ID -o $USER_NAME \
&& useradd -m -l -u $USER_ID -g $GROUP_ID -o -s /bin/bash $USER_NAME \
&& mkdir /srv/app/node_modules \
&& chown $UID:$GID /srv/app/node_modules
&& chown $USER_ID:$GROUP_ID /srv/app/node_modules

COPY ./docker-entrypoint.sh /usr/local/bin/

USER $UNAME
USER $USER_NAME

VOLUME /srv/.pnpm-store
VOLUME /srv/app
Expand Down Expand Up @@ -199,6 +199,7 @@ RUN pnpm --dir tests run test:e2e:server:node
FROM base-image AS collect

COPY --from=build-node --chown=node /srv/app/src/.output ./.output
COPY --from=build-node --chown=node /srv/app/src/scripts ./scripts
COPY --from=build-node --chown=node /srv/app/src/package.json ./package.json
# COPY --from=build-static /srv/app/package.json /dev/null
COPY --from=lint /srv/app/package.json /dev/null
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,21 @@
"debug": "4.4.3",
"nuxt-og-image": "5.1.12",
"parse5-parser-stream": "8.0.0"
},
"patchedDependencies": {
"@nuxt/content": "patches/@nuxt__content.patch"
}
},
"private": true,
"scripts": {
"build": "pnpm --dir src run build",
"build:node": "nuxt --dir src run build:node",
"build:static": "nuxt --dir src run build:static",
"dev": "pnpm --dir src run start:dev",
"dev": "pnpm --dir src run dev",
"generate": "pnpm --dir src run generate",
"lint": "pnpm -r lint",
"start": "pnpm --dir src run start",
"start:dev": "pnpm --dir src run start:dev",
"start:development": "pnpm --dir src run start:development",
"start:node": "pnpm --dir src run start:node",
"start:static": "pnpm --dir src run start:static",
"test:e2e:docker:server:dev": "pnpm --dir tests run test:e2e:docker:server:dev",
Expand Down
14 changes: 14 additions & 0 deletions patches/@nuxt__content.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
diff --git a/dist/runtime/internal/api.js b/dist/runtime/internal/api.js
index 0a41128168783b69146a578b8f89f5e02d78a923..8a07f6817c9b9f6889dbd134d5387ce73e62fcc3 100644
--- a/dist/runtime/internal/api.js
+++ b/dist/runtime/internal/api.js
@@ -6,7 +6,8 @@ async function fetchContent(event, collection, path, options) {
const fetchOptions = {
...options,
headers: {
- ...headers,
+ ...(headers.cookie ? { cookie: headers.cookie } : {}),
+ ...(event?.context.nitro.runtimeConfig?.csurf.headerName && event.context.csrfToken ? { [event.context.nitro.runtimeConfig.csurf.headerName]: event.context.csrfToken } : {}),
...options.headers
},
query: { v: checksums[String(collection)], t: import.meta.dev ? Date.now() : void 0 }
12 changes: 10 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 42 additions & 23 deletions src/.config/certificates/mkcert.sh
Original file line number Diff line number Diff line change
@@ -1,30 +1,49 @@
#!/bin/sh
THIS=$(dirname "$(readlink -f "$0")")
set -e

create() {
NAME="$1"
shift
CONTENT=$*

path="$THIS/$NAME"
certfile="$path.crt"
keyfile="$path.key"

if [ "$CONTENT" != "" ]; then
# shellcheck disable=SC2086
mkcert \
-cert-file "$certfile" \
-ecdsa \
-key-file "$keyfile" $CONTENT
fi
SCRIPT_DIRECTORY=$(dirname "$(readlink -f "$0")")
CERTIFICATE_SUFFIX="-dev"

[ -n "$CI" ] && CERTIFICATE_SUFFIX="-ci"

is_certificate_valid() {
certificate_path="$1"
root_ca_path="$(mkcert -CAROOT)/rootCA.pem"

cat "$(mkcert -CAROOT)/rootCA.pem" >> "$certfile"
[ -f "$certificate_path" ] || return 1

openssl verify -CAfile "$root_ca_path" "$certificate_path" >/dev/null 2>&1 && openssl x509 -checkend 86400 -noout -in "$certificate_path" >/dev/null 2>&1
}

echo "key crt" | tr ' ' '\n' | while read -r glob; do
if test -n "$(find "$THIS" -maxdepth 1 -name "*.$glob" -print -quit)"; then
rm "$THIS"/*."$glob"
generate_certificate() {
certificate_name="$1"
shift
domains="$*"

certificate_path="${SCRIPT_DIRECTORY}/${certificate_name}${CERTIFICATE_SUFFIX}.crt"
key_file_path="${SCRIPT_DIRECTORY}/${certificate_name}${CERTIFICATE_SUFFIX}.key"

if is_certificate_valid "$certificate_path"; then
echo "✓ Certificate '${certificate_name}' is valid"
return 0
fi

if [ -f "$certificate_path" ]; then
echo "✗ Removing outdated certificate: '${certificate_name}'"
rm -f "$certificate_path" "$key_file_path"
fi
done

create "ssl" "localhost" "app.localhost" "127.0.0.1" "0.0.0.0"
echo "→ Generating certificate: '${certificate_name}'"
# shellcheck disable=SC2086
mkcert \
-cert-file "$certificate_path" \
-key-file "$key_file_path" \
-ecdsa \
$domains

cat "$(mkcert -CAROOT)/rootCA.pem" >> "$certificate_path"

echo "✓ Certificate generated: '${certificate_name}'"
}

generate_certificate "ssl" "localhost" "app.localhost" "127.0.0.1" "0.0.0.0"
7 changes: 6 additions & 1 deletion src/.config/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,17 @@ export default defineNuxtConfig({
'/**': {
headers: { 'Document-Policy': 'js-profiling' }, // Sentry's browser profiling (currently supported for Chromium-based browsers)
},
'/__nuxt_content/content/query': {
csurf: false,
},
'/api/model/event/ical': {
csurf: false,
security: {
xssValidator: false, // TipTap's HTML is stored unescaped (is escaped when displayed) so api requests would trigger the xss protection here (https://github.com/maevsi/vibetype/issues/1603)
},
},
'/api/service/traefik/authentication': {
'/api/internal/service/postgraphile/authentication': {
csurf: false,
security: {
xssValidator: false, // TipTap's HTML is stored unescaped (is escaped when displayed) so api requests would trigger the xss protection on forward authentication (https://github.com/maevsi/vibetype/issues/1603)
},
Expand Down
29 changes: 28 additions & 1 deletion src/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,34 @@ if (!runtimeConfig.public.vio.isTesting && !isApp) {
)
}

// TODO: move the JWT code below to a composable once `useFetch` (SSR caching) works from within (https://github.com/nuxt/nuxt/issues/14736)
// initially get JWT
const { data } = await useFetch('/api/model/jwt')
store.jwtSet(data.value?.jwtDecoded)

// if JWT exists, update it
if (store.jwtDecoded) {
const csrfRequestFetch = useCsrfRequestFetch()

try {
const { jwtDecoded: jwt } = await csrfRequestFetch('/api/model/jwt', {
body: {
id: store.jwtDecoded.jti,
},
method: 'PUT',
})

if (!jwt) {
throw new Error('JWT update failed: no JWT returned')
}

store.jwtSet(jwt)
// no $urqlReset necessary as authorization is not expected to change
} catch (error) {
console.error('JWT update failed:', error)
}
}

// initialization
defineOgImageComponent(
'Default',
Expand All @@ -137,7 +165,6 @@ defineOgImageComponent(
},
)
useAppLayout()
await useAuth()
usePolyfills()
useAppGtag()
await initialize()
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/account/AccountDeleteDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ const { accountId } = defineProps<{
const { error, restart, step } = useStepper<'password' | 'success'>()

// drawer
const { signOut } = await useSignOut()
const jwtDelete = await useJwtDelete()
const isOpen = defineModel<boolean>('isOpen')
const closeDrawer = () => {
isOpen.value = false
}
const navigateToRoot = async () => {
await signOut()
await jwtDelete()
await navigateTo(localePath({ name: 'index' }))
}
const onAnimationEnd = async (isOpen: boolean) => {
Expand Down
7 changes: 6 additions & 1 deletion src/app/components/content/Content.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<template>
<LoaderIndicatorPing v-if="pending" />
<!-- TODO: use v-model error -->
<AppError
v-else-if="error"
:error="{ message: error.message, statusCode: 500 }"
/>
<AppError
v-else-if="!data"
:error="{ message: 'Data is missing', statusCode: 404 }"
Expand All @@ -17,7 +22,7 @@ const { path } = defineProps<{

// api data
const { locale } = useI18n()
const { data, pending } = await useAsyncData(() =>
const { data, pending, error } = await useAsyncData(() =>
queryCollection('content').path(`/${locale.value}/${path}`).first(),
)

Expand Down
2 changes: 2 additions & 0 deletions src/app/components/content/legal/ContentLegalTerms.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<script setup lang="ts">
import { parseMarkdown } from '@nuxtjs/mdc/runtime'
import { useQuery } from '@urql/vue'
import { consola } from 'consola'

import { graphql } from '~~/gql/generated'

Expand Down Expand Up @@ -69,6 +70,7 @@ const { data, error, pending } = await useAsyncData(
)

if (error.value) {
consola.log(error.value)
modelError.value = error.value // TODO: watch?
}

Expand Down
Loading
Loading