Skip to content

Commit a4391e7

Browse files
committed
fix: multi tenant auth should include tenantId in user/accesstoken during SSR
1 parent 37a1f12 commit a4391e7

File tree

7 files changed

+90
-11
lines changed

7 files changed

+90
-11
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@
5454
"lint": "prettier -c --parser typescript \"{src,__tests__,e2e}/**/*.[jt]s?(x)\"",
5555
"lint:fix": "pnpm run lint --write",
5656
"test:types": "tsc --build tsconfig.json",
57-
"test:unit": "vitest --coverage",
57+
"test:unit": "cross-env GOOGLE_CLOUD_PROJECT='vue-fire-store' vitest --coverage",
5858
"firebase:emulators": "firebase emulators:start",
59-
"test:dev": "vitest",
59+
"test:dev": "cross-env GOOGLE_CLOUD_PROJECT='vue-fire-store' vitest",
6060
"test": "pnpm run lint && pnpm run test:types && pnpm run build && pnpm run -C packages/nuxt build && pnpm run test:unit run",
6161
"prepare": "simple-git-hooks"
6262
},
@@ -97,6 +97,7 @@
9797
"chalk": "^5.3.0",
9898
"consola": "^3.2.3",
9999
"conventional-changelog-cli": "^2.0.34",
100+
"cross-env": "^7.0.3",
100101
"enquirer": "^2.4.1",
101102
"execa": "^8.0.1",
102103
"firebase": "^10.8.0",

packages/nuxt/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
"build": "nuxt-module-build build",
3333
"lint": "eslint src",
3434
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path . -l nuxt-vuefire -r 1",
35-
"test": "vitest",
36-
"dev": "nuxi dev playground",
35+
"test": "cross-env GOOGLE_CLOUD_PROJECT='vue-fire-store' vitest",
36+
"dev": "cross-env GOOGLE_CLOUD_PROJECT='vue-fire-store' nuxi dev playground",
3737
"dev:build": "nuxi build playground",
3838
"dev:prepare": "nuxt-module-build --stub"
3939
},
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script setup lang="ts">
2+
type Props = {
3+
data: MaybeRef<unknown>,
4+
testid: string
5+
}
6+
const props = defineProps<Props>()
7+
8+
const serverState = useState();
9+
if (import.meta.server) {
10+
serverState.value = JSON.parse(JSON.stringify(toValue(props.data)));
11+
}
12+
</script>
13+
14+
<template>
15+
<pre :data-testid="props.testid">{{ serverState }}</pre>
16+
</template>

packages/nuxt/playground/pages/authentication.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,13 @@ onMounted(() => {
151151
<!-- this is for debug purposes only, displaying it on the server would create a hydration mismatch -->
152152
<ClientOnly>
153153
<p>Current User:</p>
154-
<pre data-testid="user-data">{{ user }}</pre>
154+
<pre data-testid="user-data-client">{{ user }}</pre>
155155
</ClientOnly>
156+
157+
<ServerOnlyPre
158+
v-if="user"
159+
:data="user"
160+
testid="user-data-server"
161+
/>
156162
</main>
157163
</template>

packages/nuxt/src/runtime/auth/plugin-authenticate-user.server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
3535
// this is also undefined if the user hasn't enabled the session cookie option
3636
if (uid) {
3737
// reauthenticate if the user is not the same (e.g. invalidated)
38-
if (auth.currentUser?.uid !== uid) {
38+
// OR multi tenancy is used, otherwise tenantId won't be present in SSR accessToken
39+
if (auth.currentUser?.uid !== uid || tenant) {
3940
const customToken = await adminAuth
4041
.createCustomToken(uid)
4142
.catch((err) => {
@@ -45,6 +46,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
4546
// console.timeLog('token', `got token for ${user.uid}`)
4647
if (customToken) {
4748
logger.debug('Signing in with custom token')
49+
// Update firebase/auth tenantId to ensure it is set during SSR
50+
auth.tenantId = tenant ?? null
4851
// TODO: allow user to handle error?
4952
await signInWithCustomToken(auth, customToken)
5053
// console.timeLog('token', `signed in with token for ${user.uid}`)

packages/nuxt/tests/auth.spec.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ const { resolve } = createResolver(import.meta.url)
66

77
await setup({
88
rootDir: resolve('../playground'),
9-
build: true,
9+
build: false,
1010
server: true,
1111
browser: true,
1212
dev: true,
1313

1414
browserOptions: {
1515
type: 'chromium',
1616
launch: {
17-
headless: false,
17+
headless: true,
1818
},
1919
},
2020
})
2121

22-
describe.only('auth/multi-tenancy', async () => {
22+
describe('auth/multi-tenancy', async () => {
2323
it('should create a default tenant token if no tenant is specified', async () => {
2424
const page = await createPage('/authentication')
2525

@@ -47,7 +47,7 @@ describe.only('auth/multi-tenancy', async () => {
4747
await signinResponse
4848

4949
// 4. Assert user does in fact not have a tenant id
50-
const userData = await page.getByTestId('user-data').textContent()
50+
const userData = await page.getByTestId('user-data-client').textContent()
5151

5252
expect(userData).toBeTruthy()
5353
if (!userData) return
@@ -85,12 +85,54 @@ describe.only('auth/multi-tenancy', async () => {
8585
await signinResponse
8686

8787
// 4. Assert user does in fact not have a tenant id
88-
const userData = await page.getByTestId('user-data').textContent()
88+
const userData = await page.getByTestId('user-data-client').textContent()
8989

9090
expect(userData).toBeTruthy()
9191
if (!userData) return
9292

9393
const user = JSON.parse(userData)
9494
expect(user.tenantId).toEqual(tenantName)
9595
})
96+
97+
it('should return tenantId in server render', async () => {
98+
const page = await createPage('/authentication')
99+
const tenantName = 'tenant A'
100+
101+
// 1. Sign out, clear tenant to start clean
102+
await page.getByTestId('sign-out').click()
103+
await page.getByTestId('tenant').clear()
104+
await page.getByTestId('tenant').fill(tenantName)
105+
106+
// 2. Ensure test account exists
107+
const signupResponse = page.waitForResponse((r) =>
108+
r.url().includes('accounts:signUp')
109+
)
110+
await page.getByTestId('email-signup').fill('[email protected]')
111+
await page.getByTestId('password-signup').fill('testtest')
112+
await page.getByTestId('submit-signup').click()
113+
await signupResponse
114+
115+
// 3. Log in with test account, check tenant
116+
// Call to sign in is 'accounts:signInWithPassword', but we need __session call to get user info
117+
const signinResponse = page.waitForResponse((r) =>
118+
r.url().includes('/api/__session')
119+
)
120+
await page.getByTestId('email-signin').fill('[email protected]')
121+
await page.getByTestId('password-signin').fill('testtest')
122+
await page.getByTestId('submit-signin').click()
123+
await signinResponse
124+
125+
// 4. Reload the page to trigger server render
126+
await page.reload({ waitUntil: 'domcontentloaded' })
127+
128+
const serverUserData = await page
129+
.getByTestId('user-data-server')
130+
.textContent()
131+
132+
expect(serverUserData).toBeTruthy()
133+
if (!serverUserData) return
134+
135+
const serverUser = JSON.parse(serverUserData)
136+
expect(serverUser.tenantId).toEqual(tenantName)
137+
})
96138
})

pnpm-lock.yaml

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)