0

The backend is configured with Node.js and the front end uses Vite. We distributed what we were developing locally to Firebase Functions.

I thought there would be no problem because I used a Google Cloud Service account in the existing local area, but the API using Google Analytics alone causes 500 errors.

The following errors occur on the server terminal.

>  Error during visitor stats fetch: Error: 7 PERMISSION_DENIED: Request had insufficient authentication scopes.
>      at callErrorFromStatus (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/call.js:31:19)
>      at Object.onReceiveStatus (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/client.js:193:76)
>      at Object.onReceiveStatus (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:360:141)
>      at Object.onReceiveStatus (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/client-interceptors.js:323:181)
>      at /Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/resolving-call.js:129:78
>      at process.processTicksAndRejections (node:internal/process/task_queues:77:11)
>  for call at
>      at ServiceClientImpl.makeUnaryRequest (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/client.js:161:32)
>      at ServiceClientImpl.<anonymous> (/Users/user/Documents/dev/works/project-backend/functions/node_modules/@grpc/grpc-js/build/src/make-client.js:105:19)
>      at /Users/user/Documents/dev/works/project-backend/functions/node_modules/@google-analytics/data/build/src/v1beta/beta_analytics_data_client.js:233:29
>      at /Users/user/Documents/dev/works/project-backend/functions/node_modules/google-gax/build/src/normalCalls/timeout.js:44:16
>      at OngoingCallPromise.call (/Users/user/Documents/dev/works/project-backend/functions/node_modules/google-gax/build/src/call.js:67:27)
>      at NormalApiCaller.call (/Users/user/Documents/dev/works/project-backend/functions/node_modules/google-gax/build/src/normalCalls/normalApiCaller.js:34:19)
>      at /Users/user/Documents/dev/works/project-backend/functions/node_modules/google-gax/build/src/createApiCall.js:112:30
>      at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
>    code: 7,
>    details: 'Request had insufficient authentication scopes.',
>    metadata: Metadata {
>      internalRepr: Map(4) {
>        'endpoint-load-metrics-bin' => [Array],
>        'grpc-server-stats-bin' => [Array],
>        'google.rpc.errorinfo-bin' => [Array],
>        'grpc-status-details-bin' => [Array]
>      },
>      options: {}
>    },
>    statusDetails: [
>      ErrorInfo {
>        metadata: [Object],
>        reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT',
>        domain: 'googleapis.com'
>      }
>    ],
>    reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT',
>    domain: 'googleapis.com',
>    errorInfoMetadata: {
>      method: 'google.analytics.data.v1beta.BetaAnalyticsData.RunReport',
>      service: 'analyticsdata.googleapis.com'
>    }
>  }

When I developed it local, I didn't have any rights issues, so I thought there would be no errors when I deployed it to Firebase Functions.

enter image description here

As you can see in the picture, only visor-stats (APIs using Google Analytics) are experiencing 500 errors.

enter image description here

I didn't know the problem, so I added the authority.

enter image description here

I am using the service account and downloaded it in json format, and I entered the file path correctly in the .env file.

Is there a new permission setting that I need to do?

Of course, Google Analytics API and Google Analytics Data API are in use.

I'd appreciate it if you could help me. ㅠㅠ

I am conducting a local test with the command below.

firebase emulators:start --only functions

The functions/index.js code is as follows.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const dotenv = require('dotenv');
const app = require('./app');

// 환경 변수 설정
dotenv.config();

if (!admin.apps.length) {
    admin.initializeApp({
        credential: admin.credential.applicationDefault(),
    });
}

exports.api = functions.https.onRequest(app);

functions/app.js

const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const swaggerSetup = require('./src/config/swagger');
const dotenv = require('dotenv');
const requestRoutes = require('./src/routes/requestRoutes');
const cors = require('cors');

dotenv.config();

const authRoutes = require('./src/routes/authRoutes');
const blogRoutes = require('./src/routes/blogRoutes');
const projectRequestRoutes = require('./src/routes/projectReqRoutes');
const visitorStatsRoutes = require('./src/routes/visitorStatsRoutes');
const bakRoutes = require('./src/routes/bakRoutes');
const authController = require('./src/controllers/authController');

const app = express();

// CORS 설정
app.use(cors({
    origin: ['http://localhost:5173', 'https://yinvl-web.web.app'],
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,  // 인증 정보(쿠키 등) 포함 여부
}));

app.options('*', cors()); // Preflight 요청을 위한 CORS 설정

// Morgan 로깅 구성
const morganFormat = process.env.NODE_ENV === 'production' ? 'combined' : 'dev';
app.use(morgan(morganFormat));

// Express 설정: JSON 및 URL-encoded 페이로드 처리
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Cookie Parser
app.use(cookieParser(process.env.COOKIE_SECRET));

// Session configuration
app.use(session({
    secret: process.env.COOKIE_SECRET, // 세션 암호화 키
    resave: false,
    saveUninitialized: true,
    cookie: {
        httpOnly: true, // javascript에서 접근 못하도록 (보안에 좋음)
        secure: true
    }
}));

// Initialize Passport
app.use(authController.initializePassport);
app.use(authController.session);

// Routes
app.use('/', authRoutes);  // 로그인 및 인증 라우트
app.use('/project-requests', projectRequestRoutes);  // 고객의 프로젝트 요청 폼
app.use('/yinvl/blog', blogRoutes);  // 블로그 관련 라우트
app.use('/yinvl', requestRoutes);  // 어드민 대시보드에서 요청 조회
app.use('/yinvl', visitorStatsRoutes);  // 방문자 통계 관련 라우트
app.use('/yinvl', bakRoutes);  // 백업 관련 라우트

// Swagger API 문서화 설정
swaggerSetup(app);

module.exports = app;

0