npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dev_zone_auth

v2.0.1

Published

Middleware for Dev zone page and Dev zone API

Downloads

267

Readme

dev_zone_auth

Package dùng chung cho các app Avada / Megaplaza để bảo vệ trang/route /dev_zone bằng Firebase Google SSO (chỉ cho phép email @avada / @megaplaza) và log mọi request đi qua API /dev_zone lên BigQuery.

Package gồm 2 phần độc lập:

| Phần | Chạy ở | Vai trò | |---|---|---| | Client | Browser (React) | Provider auth + guard route + UI login + UserMenu | | Server | Node / Firebase Functions (Koa) | Middleware verify Firebase ID token + tracking BigQuery |

Toàn bộ ví dụ dưới đây lấy trực tiếp từ app blogs (SeoOn Blog) — bạn có thể sao chép nguyên xi rồi đổi đường dẫn / tên cho phù hợp với app đích.


Mục lục

  1. Cài đặt
  2. Entry points & cách import
  3. Phần Client — React
  4. Phần Server — Koa middleware
  5. BigQuery tracking
  6. Restriction domain email
  7. Service account & Firebase config
  8. Checklist tích hợp app mới
  9. Dev / Build package
  10. Troubleshooting

1. Cài đặt

# Version mới nhất trên npm
cd packages/assets
yarn add dev_zone_auth

cd packages/functions
yarn add dev_zone_auth

Peer dependencies

Consumer app phải có sẵn:

{
  "@shopify/polaris": ">=13.0.0",
  "@shopify/polaris-icons": ">=6.0.0",
  "firebase": ">=10.0.0",
  "firebase-admin": ">=12.0.0",
  "react": ">=17.0.0",
  "react-dom": ">=17.0.0",
  "react-router-dom": "^5.0.1"
}

firebase-adminoptional — chỉ cần khi consumer dùng phần server.


2. Entry points & cách import

Package publish 3 entry qua field exports trong package.json:

| Import path | Chạy ở | Re-export | |---|---|---| | dev_zone_auth | — | toàn bộ client + server | | dev_zone_auth/client | Browser | AuthPage, DevAuthGuard, DevZoneAuth, UserMenu, useAuth, useAuthToast, type AuthContextValue | | dev_zone_auth/server | Node / Firebase Functions | authenticate, authenticateForMissingToken |

Quy tắc: Code chạy browser chỉ import từ dev_zone_auth/client, code Node chỉ import từ dev_zone_auth/server. Đừng import từ root (dev_zone_auth) trong code FE — sẽ kéo cả firebase-admin vào bundle.


3. Phần Client — React

Quy trình tích hợp vào blogs/packages/assets gồm 4 bước: khai báo env → tạo file config → bọc AuthPage → dùng useAuth trong trang DevZone.

3.1. Khai báo firebaseConfig riêng cho DevZone

DevZone dùng Firebase project riêng (hiện tại là seoon-image-optimizer-staging), không dùng chung với Firebase của app (blogs dùng avada-blog-staging). Mỗi app cần đặt biến env riêng để không xung đột.

packages/assets/.env.development — thêm 7 biến:

# DevZone Firebase (shared across Avada/Megaplaza apps for /dev_zone auth)
VITE_DEVZONE_FIREBASE_API_KEY=AIzaSyDz-N1Wl9TbgQExtcHUd6l4iLXvhd3UrRg
VITE_DEVZONE_FIREBASE_AUTH_DOMAIN=seoon-image-optimizer-staging.firebaseapp.com
VITE_DEVZONE_FIREBASE_PROJECT_ID=seoon-image-optimizer-staging
VITE_DEVZONE_FIREBASE_STORAGE_BUCKET=seoon-image-optimizer-staging.appspot.com
VITE_DEVZONE_FIREBASE_MESSAGING_SENDER_ID=68856638145
VITE_DEVZONE_FIREBASE_APP_ID=1:68856638145:web:e1495b4fdb3b653de00240
VITE_DEVZONE_FIREBASE_MEASUREMENT_ID=G-D37BSMJN5N

packages/assets/src/config/devZoneAuth.js — đọc env ra object:

export const devZoneFirebaseConfig = {
  apiKey: import.meta.env.VITE_DEVZONE_FIREBASE_API_KEY,
  authDomain: import.meta.env.VITE_DEVZONE_FIREBASE_AUTH_DOMAIN,
  projectId: import.meta.env.VITE_DEVZONE_FIREBASE_PROJECT_ID,
  storageBucket: import.meta.env.VITE_DEVZONE_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: import.meta.env.VITE_DEVZONE_FIREBASE_MESSAGING_SENDER_ID,
  appId: import.meta.env.VITE_DEVZONE_FIREBASE_APP_ID,
  measurementId: import.meta.env.VITE_DEVZONE_FIREBASE_MEASUREMENT_ID
};

3.2. Bọc <AuthPage> trong layout

packages/assets/src/layouts/AppTranslate.js:

import {AppProvider} from '@shopify/polaris';
import {Router} from 'react-router-dom';
import {AuthPage, UserMenu} from 'dev_zone_auth/client';
import {devZoneFirebaseConfig} from '@assets/config/devZoneAuth';
import {history} from '@assets/history';
import Routes from '@assets/routes/routes';

function AppTranslate({isEmbedApp = false, shop}) {
  return (
    <AppProvider i18n={...} linkComponent={ReactRouterLink}>
      <Router history={history}>
        {/* các provider khác: QueryClient, UpgradePlan, MainLayout, ... */}
        <AuthPage isProduction={true} firebaseConfig={devZoneFirebaseConfig}>
          <UserMenu />
          <Routes />
        </AuthPage>
      </Router>
    </AppProvider>
  );
}

Yêu cầu vị trí trong tree:

  1. <AuthPage> phải nằm trong <Router> / <BrowserRouter> — bên trong dùng useLocation() để check pathname.
  2. <AuthPage> phải nằm trong <AppProvider> của Polaris — login page và UserMenu dùng Polaris components.
  3. firebaseConfig phải stable — khai báo ngoài component hoặc useMemo. Nếu reference thay đổi mỗi render thì auth instance sẽ re-create.

3.3. UserMenu — Avatar + Sign out

Render fixedtop: 12, right: 12, zIndex: 999. Click avatar → popover hiện tên + email + nút Sign out.

Không nhận props. Tự ẩn nếu user === null (chưa login hoặc đang ở mode isProduction=false).

Đặt trực tiếp bên trong <AuthPage> trước <Routes /> như ví dụ mục 3.2.


3.4. Dùng useAuth trong trang DevZone

packages/assets/src/pages/DevZone/index.jsx:

import {useAuth} from 'dev_zone_auth/client';

function DevZone() {
  const {
    userState: {user: userFirebase}
  } = useAuth();

  // userFirebase: { uid, email, displayName, photoURL, ... }
  return <div>Hello {userFirebase?.displayName}</div>;
}

export default DevZone;

Gọi useAuth() phải nằm trong cây <AuthPage>, ngoài sẽ throw useAuth must be used within an AuthProvider.

Full return của useAuth:

interface AuthContextValue {
  userState: { user: User | null; isAuthenticated: boolean };
  isLoading: boolean;
  isSigningIn: boolean;
  signInWithGoogle: () => Promise<void>;
  signOut: () => Promise<void>;
}

3.5. Gọi API có auth từ FE

FE phải đính kèm Firebase ID token vào header x-auth-firebase-token cho mọi request đi qua route được middleware authenticate bảo vệ. Lấy token bằng userFirebase.getIdToken()userFirebaseuser trả về từ hook useAuth(), đã được sign bởi đúng project Firebase DevZone.

packages/assets/src/pages/DevZone/index.jsx:

import {useAuth} from 'dev_zone_auth/client';
import api from '@assets/helpers/api';

function DevZone() {
  const {
    userState: {user: userFirebase}
  } = useAuth();

  const handleDevZone = async (data = {}, field) => {
    try {
      const idToken = await userFirebase.getIdToken();
      await api(`/dev_zone?type=${field}`, {
        headers: {
          'x-auth-firebase-token': idToken
        },
        body: {data},
        method: 'PUT'
      });
    } catch (e) {
      // ...
    }
  };
}

Token Firebase ID hết hạn 1h — luôn gọi getIdToken() ngay trước khi request, đừng cache.

⚠️ Phải dùng userFirebase từ useAuth(), không dùng auth.currentUser của app bình thường — hai Firebase project khác nhau, token sẽ fail khi server verify.


3.6. Tham khảo props

<AuthPage>

| Prop | Type | Bắt buộc | Default | Mô tả | |---|---|---|---|---| | children | React.ReactNode | ✅ | — | Cây component bên trong (thường là <Routes/>) | | isProduction | boolean | ⚠️ Nên truyền | true | false → bypass hoàn toàn Firebase auth (local dev). true → chạy thật onAuthStateChanged + Google popup | | firebaseConfig | FirebaseOptions | ✅ | — | Firebase web config của project DevZone. Bắt buộc có projectId; package cache auth instance theo projectId |

Các export khác từ dev_zone_auth/client

| Export | Loại | Mô tả | |---|---|---| | DevAuthGuard | Component | Guard route (đã lồng sẵn trong AuthPage, chỉ import khi tự build provider) | | DevZoneAuth | Component | Trang login Google (auto render khi chưa authen) | | UserMenu | Component | Avatar + popover Sign out | | useAuth | Hook | State + action auth | | useAuthToast | Hook | State toast (cho login page tự build) | | AuthContextValue | Type | Type return của useAuth |


4. Phần Server — Koa middleware

4.1. Chuẩn bị service account DevZone

Service account phải là của project Firebase DevZone (không phải của app blogs). Lấy từ GCP Console của project DevZone → IAM → Service Accounts → tạo key JSON.

Quyền tối thiểu:

| Quyền | Dùng để | |---|---| | firebaseauth.admin (hoặc Firebase Authentication Admin) | Verify Firebase ID token (adminAuth.verifyIdToken) | | bigquery.dataEditor | Insert row + tự tạo dataset dev_zone_logs.ui_events khi cần |

Đặt file trong blogs:

packages/functions/serviceAccount.devzone.json

Thêm vào .gitignore (của blogs, không commit):

serviceAccount.devzone.json
serviceAccount*.json

4.2. Khai báo devZoneAuthDefaults

authenticate / authenticateForMissingToken đều nhận cùng 1 bộ options trong cùng 1 app, gom vào 1 constant để dùng chung:

packages/functions/src/routes/api.js:

import Router from 'koa-router';
import {authenticate, authenticateForMissingToken} from 'dev_zone_auth/server';
import app from '@functions/config/app';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const devZoneServiceAccount = require('../../serviceAccount.devzone.json');

const devZoneAuthDefaults = {
  appId: 'seoon-blog',         // ghi vào cột app_id BigQuery
  isProduction: true,           // true = verify thật (xem lưu ý bên dưới)
  serviceAccount: devZoneServiceAccount
};

Lưu ý isProduction: true: Trong blogs hiện set cứng true cho cả dev lẫn prod → route /dev_zone luôn bắt token kể cả local. Nếu muốn bypass khi dev, đổi thành isProduction: app.isProduction.


4.3. authenticate(options) — bắt buộc token

Route chỉ cho phép user đã login DevZone vào.

Flow

  1. isProduction === falsenext() luôn (bypass).
  2. Đọc header x-auth-firebase-token.
    • Không có → 401 {success: false, message: "Missing authentication token"}.
  3. adminAuth.verifyIdToken(token) (cache Firebase Admin app theo project_id).
  4. Email không chứa @avada / @megaplaza403 Access denied.
  5. Pass → fire-and-forget trackEvent(...) lên BigQuery → await next().
  6. Lỗi:
    • auth/id-token-expired / auth/id-token-revoked / auth/argument-error401 Invalid or expired token.
    • Khác → 500 Internal server error.

Ví dụ (blogs)

router.put('/dev_zone', authenticate(devZoneAuthDefaults), devZoneController.update);

4.4. authenticateForMissingToken(options) — opportunistic

Khác authenticate: không có token → vẫn next(). Chỉ verify + log khi client có gửi token.

Dùng cho route public muốn tracking nếu user đã login DevZone, không bắt buộc.

Flow

  1. isProduction === falsenext() luôn.
  2. Không có header → next() luôn (không log).
  3. Có token → verify, log BigQuery (cột action = ""), rồi next().
  4. Lỗi (kể cả token sai) → log error → next(). Tuyệt đối không chặn request.

Ví dụ (blogs)

router.post(
  '/article/create',
  authenticateForMissingToken(devZoneAuthDefaults),
  articleController.create
);

router.post(
  '/shop',
  jsonType,
  authenticateForMissingToken(devZoneAuthDefaults),
  shopController.updateShop
);

5. BigQuery tracking

Mỗi request qua authenticate (và authenticateForMissingToken khi có token) sẽ insert 1 row vào bảng <project>.dev_zone_logs.ui_events trong project của service account.

Schema

| Column | Type | Nội dung | |---|---|---| | app_id | STRING | options.appId (vd "seoon-blog") | | user_id | STRING | Firebase UID | | email | STRING | Email user | | action | STRING | JSON.stringify(ctx.query). Với authenticateForMissingToken luôn là "" | | path | STRING | ctx.path | | method | STRING | HTTP method | | metadata | STRING | JSON.stringify(ctx.request.body) (hoặc null) | | created_at | TIMESTAMP | ISO timestamp |

Field action

Middleware không fix cứng key nào — JSON.stringify toàn bộ ctx.query:

| Request | action được log | |---|---| | /api/dev_zone?x=click | {"x":"click"} | | /api/dev_zone?action=view&page=home | {"action":"view","page":"home"} | | /api/dev_zone (không query) | {} |

Auto-create dataset & table

Dataset dev_zone_logs + table ui_events tự tạo khi insert lần đầu nếu chưa tồn tại — xem createDatasetAndTable trong src/server/bigquery.ts. Service account phải có quyền bigquery.dataEditor.


6. Restriction domain email

Hard-code trong src/pages/AuthPage.tsxsrc/server/middleware.ts:

const ALLOWED_DOMAINS = ["@avada", "@megaplaza"];

Email không chứa một trong 2 chuỗi này sẽ bị reject:

  • Client: hiện toast Access denied. Only Avada email accounts are allowed.
  • Server: trả 403 Access denied.

Thêm domain mới → sửa cả 2 chỗ trong package rồi yarn build lại.


7. Service account & Firebase config

Package không bundle service account hay firebase config. Mỗi consumer phải tự cung cấp.

Client — firebaseConfig

| Nguồn | Khuyến nghị khi | |---|---| | Env vars (VITE_DEVZONE_FIREBASE_*) | Mặc định — mỗi env (dev/staging/prod) có thể dùng project khác nhau | | Hardcode trong file config/devZoneAuth.js | Chỉ khi chắc chắn 1 project duy nhất cho mọi env |

Firebase web config (apiKey, appId…) không phải secret theo Firebase — có thể commit vào source, nhưng tốt nhất vẫn đi qua env để linh hoạt.

Server — serviceAccount

| Nguồn | Khuyến nghị khi | |---|---| | File JSON (require('../../serviceAccount.devzone.json')) | Deploy truyền thống, file sync qua secret store / manual copy. Phải thêm vào .gitignore | | Env var chứa JSON | Firebase Functions / Cloud Run — JSON.parse(process.env.DEVZONE_SA_JSON) | | Google Secret Manager | Có infra Secret Manager sẵn — load lúc boot |

⚠️ Không commit service account vào git.

Đa project trong cùng process

Package cache theo project_id:

  • Firebase Admin app theo serviceAccount.project_id
  • BigQuery client theo serviceAccount.project_id
  • Firebase client (browser) theo firebaseConfig.projectId

→ Nhiều app cùng chạy trong 1 Node process với SA khác nhau vẫn an toàn, không đè lên nhau.


8. Checklist tích hợp app mới

Client

  • [ ] yarn add dev_zone_auth@^2.0.0 (hoặc file:... / npm version)
  • [ ] Thêm 7 biến VITE_DEVZONE_FIREBASE_* vào .env.development (và .env.production nếu có)
  • [ ] Tạo src/config/devZoneAuth.js export devZoneFirebaseConfig
  • [ ] Bọc <AuthPage firebaseConfig={devZoneFirebaseConfig} isProduction={...}> trong <Router> + <AppProvider>
  • [ ] Đặt <UserMenu /><Routes /> bên trong <AuthPage>
  • [ ] Có ít nhất 1 route có pathname chứa /dev_zone
  • [ ] Dùng useAuth() trong trang DevZone nếu cần user.email / user.uid
  • [ ] Mọi fetch đi qua route được guard: thêm header x-auth-firebase-token: <idToken>

Server

  • [ ] Copy serviceAccount.devzone.json (của project DevZone) vào packages/functions/
  • [ ] Thêm serviceAccount*.json vào .gitignore
  • [ ] Service account có quyền firebaseauth.admin + bigquery.dataEditor
  • [ ] Khai báo devZoneAuthDefaults đầu file routes/api.js với appId của app
  • [ ] authenticate(devZoneAuthDefaults) cho route bắt buộc auth
  • [ ] authenticateForMissingToken(devZoneAuthDefaults) cho route public có tracking
  • [ ] Cân nhắc isProduction: app.isProduction để bypass khi dev local

9. Dev / Build package

yarn dev       # vite playground ở thư mục dev/
yarn build     # build tsup → dist/
yarn watch     # build watch
yarn lint      # eslint --fix
yarn test      # jest

Sau khi sửa source avada-authyarn buildrestart consumer (Firebase Functions / Koa app / Vite dev server) để load dist mới. Node module cache không tự refresh.


10. Troubleshooting

| Triệu chứng | Nguyên nhân hay gặp | |---|---| | Trang /dev_zone luôn hiện login dù đã login | AuthPage không nằm trong <Router>useLocation lỗi, hoặc email không match @avada/@megaplaza | | Dev local cũng bị bắt Google sign-in | isProduction không truyền / truyền true cho AuthPage | | useAuth must be used within an AuthProvider | Component gọi useAuth() không nằm trong <AuthPage> | | API trả 401 Missing authentication token | FE chưa gắn header x-auth-firebase-token, hoặc giá trị rỗng | | API trả 401 Invalid or expired token | Token đã hết hạn (>1h) — FE phải gọi lại getIdToken() trước mỗi request | | API trả 403 Access denied | Token hợp lệ nhưng email không thuộc domain whitelist | | BigQuery không có row mới | 1) Consumer chưa restart sau khi rebuild dist; 2) request bị chặn 401/403 trước khi tới trackEvent; 3) SA thiếu quyền BigQuery; 4) void trackEvent bị cắt khi Cloud Function kết thúc quá nhanh — xem log [error bigquery.trackEvent] | | dist/client / dist/server không tồn tại | Chưa chạy yarn build sau khi clone/install | | Bundle FE bị kéo firebase-admin → fail build | Đang import từ root dev_zone_auth thay vì dev_zone_auth/client | | [dev_zone_auth] serviceAccount is missing project_id/... | Object serviceAccount truyền vào không hợp lệ — check lại path require / nội dung file | | [dev_zone_auth] firebaseConfig.projectId is required | Env VITE_DEVZONE_FIREBASE_PROJECT_ID rỗng — check .env và restart Vite | | Token verify thất bại với auth/argument-error | FE đang dùng Firebase project khác với SA server → user login vào project A nhưng server verify bằng SA của project B. Đảm bảo firebaseConfig FE và serviceAccount BE cùng project DevZone |