Skip to content

Commit 70e69fe

Browse files
committed
feat(nuxt): support admin-sdk appcheck
1 parent 66cd6d7 commit 70e69fe

File tree

5 files changed

+108
-13
lines changed

5 files changed

+108
-13
lines changed

packages/nuxt/playground/nuxt.config.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { fileURLToPath } from 'node:url'
22
import { resolve } from 'node:path'
3-
import VueFire from 'nuxt-vuefire'
43

54
// we need the root node modules where packages are hoisted
65
const nodeModules = fileURLToPath(
@@ -15,6 +14,9 @@ export default defineNuxtConfig({
1514

1615
alias: {
1716
// import the dev version directly
17+
'vuefire/server': fileURLToPath(
18+
new URL('../../../src/server/index.ts', import.meta.url)
19+
),
1820
vuefire: fileURLToPath(new URL('../../../src/index.ts', import.meta.url)),
1921
},
2022

@@ -41,6 +43,18 @@ export default defineNuxtConfig({
4143
appId: '1:998674887640:web:1e2bb2cc3e5eb2fc3478ad',
4244
measurementId: 'G-RL4BTWXKJ7',
4345
},
46+
47+
admin: {
48+
config: {},
49+
serviceAccount: resolve(
50+
fileURLToPath(
51+
new URL(
52+
'./vue-fire-store-firebase-adminsdk.json',
53+
import.meta.url
54+
)
55+
)
56+
),
57+
},
4458
},
4559
],
4660
],

packages/nuxt/src/module.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from '@nuxt/kit'
99
import type { NuxtModule } from '@nuxt/schema'
1010
import type { FirebaseOptions } from '@firebase/app-types'
11+
import type { AppOptions, ServiceAccount } from 'firebase-admin'
1112
import type { NuxtVueFireAppCheckOptions } from './app-check'
1213

1314
export interface VueFireNuxtModuleOptions {
@@ -18,15 +19,36 @@ export interface VueFireNuxtModuleOptions {
1819
*/
1920
optionsApiPlugin?: boolean | 'firestore' | 'database'
2021

22+
/**
23+
* Firebase Options passed to `firebase/app`'s `initializeApp()`.
24+
*/
2125
config?: FirebaseOptions
2226

27+
/**
28+
* Firebase Admin Options.
29+
*/
30+
admin?: {
31+
/**
32+
* Firebase Admin Options passed to `firebase-admin`'s `initializeApp()`. Required if you are using the auth, or the
33+
* app-check module.
34+
*/
35+
config: Omit<AppOptions, 'credential'>
36+
37+
/**
38+
* Firebase Admin Service Account passed to `firebase-admin`'s `initializeApp()`. Required if you are adding an adminConfig
39+
*/
40+
serviceAccount: string | ServiceAccount
41+
}
42+
2343
/**
2444
* Optional name passed to `firebase.initializeApp(config, name)`
2545
*/
26-
appName?: string
46+
// TODO: is this useful?
47+
// appName?: string
2748

2849
/**
29-
* Enables AppCheck
50+
* Enables AppCheck on the client and server. Note you only need to pass the options for the client, on the server,
51+
* the configuration will be handled automatically.
3052
*/
3153
appCheck?: NuxtVueFireAppCheckOptions
3254

@@ -67,11 +89,25 @@ const VueFire: NuxtModule<VueFireNuxtModuleOptions> =
6789

6890
// Let plugins and the user access the firebase config within the app
6991
nuxt.options.appConfig.firebaseConfig = options.config
70-
nuxt.options.appConfig.appCheck = options.appCheck
92+
nuxt.options.appConfig.vuefireOptions = options
7193

7294
// nuxt.options.build.transpile.push(templatesDir)
7395
nuxt.options.build.transpile.push(runtimeDir)
7496

97+
if (nuxt.options.ssr && options.admin) {
98+
// check the provided config is valid
99+
if (options.auth || options.appCheck) {
100+
if (!options.admin.config || !options.admin.serviceAccount) {
101+
throw new Error(
102+
'[VueFire]: Missing firebase "admin" config. Provide an "admin" option to the VueFire module options. This is necessary to use the auth or app-check module.'
103+
)
104+
}
105+
nuxt.options.appConfig.firebaseAdmin = options.admin
106+
}
107+
108+
addPlugin(resolve(runtimeDir, '2.admin-plugin.server.ts'))
109+
}
110+
75111
nuxt.hook('modules:done', () => {
76112
// addPlugin(resolve(runtimeDir, 'plugin'))
77113

@@ -101,8 +137,17 @@ declare module '@nuxt/schema' {
101137
firebaseConfig: FirebaseOptions
102138

103139
/**
104-
* AppCheck options passed to VueFire module.
140+
* VueFireNuxt options used within plugins.
141+
* @internal
142+
*/
143+
vuefireOptions: Pick<VueFireNuxtModuleOptions, 'appCheck' | 'auth'>
144+
145+
/**
146+
* Firebase Admin options passed to VueFire module. Only available on the server.
105147
*/
106-
appCheck?: NuxtVueFireAppCheckOptions
148+
firebaseAdmin?: {
149+
config: Omit<AppOptions, 'credential'>
150+
serviceAccount: string | ServiceAccount
151+
}
107152
}
108153
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import admin from 'firebase-admin'
2+
import { VueFireAppCheckServer } from 'vuefire/server'
3+
import { defineNuxtPlugin, useAppConfig } from '#app'
4+
5+
export default defineNuxtPlugin((nuxtApp) => {
6+
const appConfig = useAppConfig()
7+
8+
const { firebaseConfig, firebaseAdmin, vuefireOptions } = appConfig
9+
10+
// the admin sdk is not always needed
11+
if (!firebaseAdmin?.config) {
12+
return
13+
}
14+
15+
// only initialize the admin sdk once
16+
if (!admin.apps.length) {
17+
const adminApp = admin.initializeApp({
18+
...firebaseAdmin.config,
19+
credential: admin.credential.cert(firebaseAdmin.serviceAccount),
20+
})
21+
22+
if (vuefireOptions.appCheck) {
23+
if (!firebaseConfig.appId) {
24+
throw new Error(
25+
'[VueFire]: Missing "appId" in firebase config. This is necessary to use the app-check module on the server.'
26+
)
27+
}
28+
VueFireAppCheckServer(adminApp, firebaseConfig.appId)
29+
}
30+
}
31+
32+
return {
33+
adminApp: admin.app(),
34+
}
35+
})

packages/nuxt/templates/plugin.ejs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ export default defineNuxtPlugin((nuxtApp) => {
3737

3838
const firebaseApp = initializeApp(firebaseConfig)
3939

40-
console.log('appcheck debug', appConfig.appCheck.debug)
41-
4240
// TODO: emulator option
4341
// connectFirestoreEmulator(getFirestore(firebaseApp), 'localhost', 8080)
4442
// connectDatabaseEmulator(getDatabase(firebaseApp), 'localhost', 8081)
@@ -51,9 +49,12 @@ export default defineNuxtPlugin((nuxtApp) => {
5149
<% } %>
5250
<% if(options.appCheck) { %>
5351
VueFireAppCheck({
54-
debug: <%= options.appCheck.debug %>,
55-
isTokenAutoRefreshEnabled: <%= options.appCheck.isTokenAutoRefreshEnabled %>,
56-
provider: new ReCaptchaV3Provider('<%= options.appCheck.key %>'),
52+
...appConfig.vuefireOptions.appCheck,
53+
provider: process.isClient
54+
? new ReCaptchaV3Provider('<%= options.appCheck.key %>')
55+
: new CustomProvider({
56+
getToken: () => Promise.resolve({ token: '', expireTimeMillis: 1 })
57+
}),
5758
}),
5859
<% } %>
5960
],

src/app-check/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const AppCheckTokenInjectSymbol: InjectionKey<Ref<string | undefined>> =
1212
Symbol('app-check-token')
1313

1414
/**
15-
* The current app-check token as a `Ref`. Note this is undefined on the server.
15+
* The current app-check token as a `Ref`. Note this ref is always undefined on the server.
1616
*/
1717
export function useAppCheckToken() {
1818
return inject(AppCheckTokenInjectSymbol)!
@@ -47,7 +47,7 @@ export function VueFireAppCheck(options: VueFireAppCheckOptions) {
4747
const token = getGlobalScope(firebaseApp, app).run(() => ref<string>())!
4848
app.provide(AppCheckTokenInjectSymbol, token)
4949

50-
// AppCheck is client only as it relies on browser apis
50+
// AppCheck requires special treatment on the server
5151
if (!isClient) return
5252

5353
if (options.debug) {

0 commit comments

Comments
 (0)