Skip to content

Commit

Permalink
chore: migrate from lucia to nuxt-auth-utils (#2546)
Browse files Browse the repository at this point in the history
<!--
☝️ PR title should follow conventional commits
(https://conventionalcommits.org).
In particular, the title should start with one of the following types:

- docs: 📖 Documentation (updates to the documentation or readme)
- fix: 🐞 Bug fix (a non-breaking change that fixes an issue)
- feat: ✨ New feature/enhancement (a non-breaking change that adds
functionality or improves existing one)
- feat!/fix!: ⚠️ Breaking change (fix or feature that would cause
existing functionality to change)
- chore: 🧹 Chore (updates to the build process or auxiliary tools and
libraries)
-->

### 🔗 Linked issue

<!-- If it resolves an open issue, please link the issue here. For
example "Resolves #123" -->

### 📚 Description

Reasons:
- Lucia mainly handles session management, but for this nuxt has h3
- nuxt-auth provides better integration with nuxt
- nuxt-auth provides passkey etc
- Lucia has some conventions that make it hard to work around it
(especially the whole "prisma adapter" thing, that is not quite working
well) - doesn't get really better with v4 see
#2335

<!-- Describe your changes in detail -->
<!-- Why is this change required? What problem does it solve? -->
  • Loading branch information
tobiasdiez authored Oct 4, 2024
1 parent 9644914 commit bd2e82b
Show file tree
Hide file tree
Showing 28 changed files with 874 additions and 485 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ pip install azure-identity azure-mgmt-web azure-mgmt-storage azure-mgmt-applicat
az login
export SUBSCRIPTION_ID=<...>
export DATABASE_URL=<...>
export AZURE_SESSION_SECRET=<...>
export NUXT_SESSION_PASSWORD=<...>
python3 .github/scripts/deploy.py --env dev -v
```
4 changes: 2 additions & 2 deletions .github/scripts/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def main(environment_name: str, verbose: bool = False):
APP_INSIGHTS_NAME = "jabref-online"
REDIS_NAME = "jabref"
DATABASE_URL = os.environ.get("DATABASE_URL", "<Not specified>")
SESSION_SECRET = os.environ.get("AZURE_SESSION_SECRET", "<Not specified>")
SESSION_SECRET = os.environ.get("NUXT_SESSION_PASSWORD", "<Not specified>")
GITHUB_REPO_TOKEN = os.environ.get("GITHUB_REPO_TOKEN", "<Not specified>")

function_app_name = "jabref-function-" + environment_name
Expand Down Expand Up @@ -132,7 +132,7 @@ def main(environment_name: str, verbose: bool = False):
"name": "REDIS_PASSWORD",
"value": redis_keys.primary_key,
},
{"name": "SESSION_SECRET_PRIMARY", "value": SESSION_SECRET},
{"name": "NUXT_SESSION_PASSWORD", "value": SESSION_SECRET},
{"name": "GITHUB_REPO_TOKEN", "value": GITHUB_REPO_TOKEN},
# Disable indexing of non-production sites
# https://nuxtseo.com/robots/guides/disable-indexing#preview-staging-testing-environments
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ jobs:
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/jabref?schema=public
GITHUB_REPO_TOKEN: ${{ secrets.GITHUBS_REPO_TOKEN }}
NUXT_SESSION_PASSWORD: somerandompasswordNxFHaqCSPpBe6n5kRz2dru4hJ7K9bjgEtmsV8QAT3MDXcUfWGL

steps:
- name: Checkout
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/deploy-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ jobs:
python .github\scripts\deploy.py --env $env:PREVIEW_NAME
env:
SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_SESSION_SECRET: ${{ secrets.AZURE_SESSION_SECRET }}

- name: Deploy Function App
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
env:
DATABASE_URL: ${{ matrix.environment == 'Test' && secrets.AZURE_TEST_DATABASE_URL || secrets.AZURE_DATABASE_URL }}
GITHUB_REPO_TOKEN: ${{ secrets.GITHUBS_REPO_TOKEN }}
NUXT_SESSION_PASSWORD: ${{ secrets.AZURE_SESSION_SECRET }}

steps:
- name: Checkout
Expand Down Expand Up @@ -91,7 +92,6 @@ jobs:
python .github\scripts\deploy.py --env ${{ matrix.deployment_environment }}
env:
SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_SESSION_SECRET: ${{ secrets.AZURE_SESSION_SECRET }}

- name: Deploy Function App
run: |
Expand Down
33 changes: 33 additions & 0 deletions auth.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
declare module '#auth-utils' {
/**
* The public information about a user stored in the session
*/
interface User {
id: string
}

/**
* The public information about the current session
*/
interface UserSession {
/**
* The secure data associated with the session, only accessible on the server
*/
server: ServerSessionData
}

/**
* Private information about the current session, only accessible on the server
* (exposed in an encrypted form to the client)
*/
interface SecureSessionData {}

/**
* The data stored for the session on the server
*/
interface ServerSessionData {
lastActive: Date
}
}

export {}
6 changes: 2 additions & 4 deletions config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ export interface Config {
}
emailClient?: string
session: {
primarySecret: string
secondarySecret: string
secret: string
}
githubRepoToken: string
public: {
Expand All @@ -85,8 +84,7 @@ export function constructConfig() {
},
emailClient: process.env.EMAIL_CLIENT,
session: {
primarySecret: process.env.SESSION_SECRET_PRIMARY ?? 'session_secret',
secondarySecret: process.env.SESSION_SECRET_SECONDARY ?? 'session_secret',
secret: process.env.NUXT_SESSION_PASSWORD ?? 'session_secret',
},
githubRepoToken: process.env.GITHUB_REPO_TOKEN ?? 'UNDEFINED',
public: {
Expand Down
28 changes: 3 additions & 25 deletions middleware/authenticated.global.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,16 @@
import { gql } from '@apollo/client/core'

/**
* Plugin that adds checks if the user is logged in, and redirects her to the login page if not.
*/
export default defineNuxtRouteMiddleware(async (to, _from) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
const { $apolloClient } = useNuxtApp()
try {
// TODO: Only call this if we have a session cookie?
const response = await $apolloClient.query({
query: gql(/* GraphQL */ `
query CheckLoggedIn {
me {
id
}
}
`),
})

// If the user is not authenticated, then redirect
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
response.data?.me?.id === undefined ||
response.errors !== undefined
) {
return redirectToLogin()
}
} catch {
const { loggedIn } = useUserSession()
if (!loggedIn.value) {
return redirectToLogin()
}
}
})

async function redirectToLogin() {
// TODO: Remember the intended url by appending something like ?redirect=context.route.fullPath
return await navigateTo({ path: '/user/login' })
return await navigateTo('/user/login')
}
7 changes: 7 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ export default defineNuxtConfig({
// Preset for configuring SEO
// https://nuxtseo.com/nuxt-seo
'@nuxtjs/seo',
// Add authentication support
// https://github.com/atinux/nuxt-auth-utils
'nuxt-auth-utils',
],

/*
Expand Down Expand Up @@ -238,6 +241,10 @@ export default defineNuxtConfig({
define: {
'process.env': {},
},
optimizeDeps: {
// Workaround for https://github.com/nuxt/nuxt/issues/27544
exclude: ['vee-validate'],
},
server: {
// Configure vite for HMR with Gitpod
// Taken from https://github.com/vitejs/vite/issues/1653#issuecomment-1079322770
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@
"@azure/communication-email": "1.0.0",
"@graphql-tools/schema": "10.0.6",
"@he-tree/vue": "2.8.7",
"@lucia-auth/adapter-prisma": "3.0.2",
"@lucia-auth/adapter-session-unstorage": "2.1.0",
"@nuxtjs/tailwindcss": "6.12.1",
"@pinia/nuxt": "0.5.3",
"@popperjs/core": "2.11.8",
Expand All @@ -69,7 +67,6 @@
"ioredis": "5.4.1",
"json-bigint-patch": "0.0.8",
"lodash": "4.17.21",
"lucia": "2.7.7",
"pinia": "2.2.1",
"reflect-metadata": "0.2.2",
"ts-loader": "9.5.1",
Expand All @@ -83,6 +80,7 @@
"zod": "3.23.8"
},
"devDependencies": {
"@adonisjs/hash": "^9.0.5",
"@apollo/utils.keyvaluecache": "3.1.0",
"@azure/core-rest-pipeline": "1.17.0",
"@azure/static-web-apps-cli": "2.0.1",
Expand Down Expand Up @@ -125,6 +123,7 @@
"@vue/compiler-sfc": "3.5.10",
"@vue/runtime-dom": "3.5.10",
"@vue/test-utils": "2.4.6",
"argon2": "^0.41.1",
"chalk": "5.3.0",
"chromatic": "11.11.0",
"concurrently": "9.0.1",
Expand All @@ -143,6 +142,7 @@
"mount-vue-component": "0.10.2",
"naive-ui": "2.40.1",
"nuxt": "3.12.4",
"nuxt-auth-utils": "^0.4.2",
"nuxt-graphql-server": "3.1.4",
"nuxt-icon": "0.6.10",
"oxlint": "0.9.9",
Expand Down
45 changes: 25 additions & 20 deletions pages/user/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
:validation-status="errors.email ? 'error' : undefined"
>
<n-input
v-model:value="values.email"
v-model:value="email"
v-bind="emailAttrs"
v-focus
/>
</n-form-item>
Expand All @@ -45,7 +46,8 @@
:validation-status="errors.password ? 'error' : undefined"
>
<n-input
v-model:value="values.password"
v-model:value="password"
v-bind="passwordAttrs"
type="password"
show-password-on="mousedown"
/>
Expand Down Expand Up @@ -101,17 +103,15 @@ definePageMeta({ layout: false })
// TODO: Automatically go to home if already logged in
// middleware: 'guest',
const { handleSubmit, errors, values } = useForm({
const { handleSubmit, errors, defineField } = useForm({
validationSchema: toTypedSchema(LoginInputSchema),
})
const [email, emailAttrs] = defineField('email')
const [password, passwordAttrs] = defineField('password')
const otherError = ref('')
const {
mutate: loginUser,
onDone,
error: graphqlError,
} = useMutation(
const { mutate: loginUser, error: graphqlError } = useMutation(
gql(/* GraphQL */ `
mutation Login($input: LoginInput!) {
login(input: $input) {
Expand Down Expand Up @@ -140,23 +140,28 @@ const {
},
},
)
onDone((result) => {
if (result.data?.login?.__typename === 'UserReturned') {
void navigateTo({ name: 'dashboard' })
} else {
otherError.value =
result.data?.login?.__typename === 'InputValidationProblem' &&
result.data.login.problems[0]
? result.data.login.problems[0].message
: 'Unknown error'
}
})
const error = computed(() => graphqlError.value ?? otherError.value)
// TODO: Implement remember login
const rememberLogin = ref(false)
const onSubmit = handleSubmit(async (values) => {
await loginUser({ input: values })
// Reset errors
otherError.value = ''
const result = await loginUser({ input: values })
if (result?.data?.login?.__typename === 'UserReturned') {
// Update user info
const { fetch } = useUserSession()
await fetch()
await navigateTo({ name: 'dashboard' })
} else {
otherError.value =
result?.data?.login?.__typename === 'InputValidationProblem' &&
result.data.login.problems[0]
? result.data.login.problems[0].message
: 'Unknown error'
}
})
</script>
Loading

0 comments on commit bd2e82b

Please sign in to comment.