@dalgoridim/headless-cms 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/auth/google/client/index.tsx
22
+ var client_exports = {};
23
+ __export(client_exports, {
24
+ GoogleAuthProvider: () => GoogleAuthProvider,
25
+ useGoogleAuth: () => useGoogleAuth
26
+ });
27
+ module.exports = __toCommonJS(client_exports);
28
+ var import_react = require("react");
29
+ var import_client = require("@dalgoridim/headless-cms/client");
30
+ var import_jsx_runtime = require("react/jsx-runtime");
31
+ var GSI_SRC = "https://accounts.google.com/gsi/client";
32
+ var GoogleAuthContext = (0, import_react.createContext)(
33
+ void 0
34
+ );
35
+ function setCookie(name, value) {
36
+ document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;
37
+ }
38
+ function deleteCookie(name) {
39
+ document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
40
+ }
41
+ function readCookie(name) {
42
+ for (const part of document.cookie.split(";")) {
43
+ const [k, ...v] = part.trim().split("=");
44
+ if (k === name) return decodeURIComponent(v.join("="));
45
+ }
46
+ return null;
47
+ }
48
+ function decodeJwt(token) {
49
+ try {
50
+ const payload = token.split(".")[1];
51
+ const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
52
+ const data = JSON.parse(json);
53
+ return data;
54
+ } catch (e) {
55
+ return null;
56
+ }
57
+ }
58
+ function GoogleAuthProvider({
59
+ children,
60
+ clientId,
61
+ adminEmails,
62
+ cookieName = "adminToken",
63
+ onLogout
64
+ }) {
65
+ const [user, setUser] = (0, import_react.useState)(null);
66
+ const [isAdmin, setIsAdmin] = (0, import_react.useState)(false);
67
+ const [isEditing, setIsEditing] = (0, import_react.useState)(false);
68
+ const [ready, setReady] = (0, import_react.useState)(false);
69
+ const [failed, setFailed] = (0, import_react.useState)(false);
70
+ const allowed = (0, import_react.useRef)(
71
+ (adminEmails != null ? adminEmails : []).map((e) => e.trim().toLowerCase())
72
+ );
73
+ const applyToken = (0, import_react.useCallback)(
74
+ (token) => {
75
+ var _a;
76
+ const claims = decodeJwt(token);
77
+ if (!claims) return;
78
+ if (claims.exp && claims.exp * 1e3 <= Date.now()) {
79
+ deleteCookie(cookieName);
80
+ return;
81
+ }
82
+ const email = (_a = claims.email) == null ? void 0 : _a.toLowerCase();
83
+ const admin = allowed.current.length === 0 ? true : !!email && allowed.current.includes(email);
84
+ setCookie(cookieName, token);
85
+ setUser({
86
+ sub: claims.sub,
87
+ email: claims.email,
88
+ name: claims.name,
89
+ picture: claims.picture
90
+ });
91
+ setIsAdmin(admin);
92
+ },
93
+ [cookieName]
94
+ );
95
+ (0, import_react.useEffect)(() => {
96
+ const existing = readCookie(cookieName);
97
+ if (existing) applyToken(existing);
98
+ }, [cookieName, applyToken]);
99
+ (0, import_react.useEffect)(() => {
100
+ let cancelled = false;
101
+ function tryInit() {
102
+ var _a, _b;
103
+ if (cancelled || !((_b = (_a = window.google) == null ? void 0 : _a.accounts) == null ? void 0 : _b.id)) return false;
104
+ window.google.accounts.id.initialize({
105
+ client_id: clientId,
106
+ callback: (res) => applyToken(res.credential),
107
+ auto_select: false,
108
+ cancel_on_tap_outside: true
109
+ });
110
+ setReady(true);
111
+ return true;
112
+ }
113
+ if (tryInit()) return;
114
+ if (!document.querySelector(`script[src="${GSI_SRC}"]`)) {
115
+ const script = document.createElement("script");
116
+ script.src = GSI_SRC;
117
+ script.async = true;
118
+ script.defer = true;
119
+ document.head.appendChild(script);
120
+ }
121
+ const startedAt = Date.now();
122
+ const timer = setInterval(() => {
123
+ if (tryInit()) {
124
+ clearInterval(timer);
125
+ } else if (Date.now() - startedAt > 1e4) {
126
+ clearInterval(timer);
127
+ if (!cancelled) setFailed(true);
128
+ }
129
+ }, 120);
130
+ return () => {
131
+ cancelled = true;
132
+ clearInterval(timer);
133
+ };
134
+ }, [clientId, applyToken]);
135
+ (0, import_react.useEffect)(() => {
136
+ const originalFetch = window.fetch;
137
+ window.fetch = async (...args) => {
138
+ const response = await originalFetch(...args);
139
+ if (response.status === 401) {
140
+ try {
141
+ const data = await response.clone().json();
142
+ if (data == null ? void 0 : data.logout) doLogout();
143
+ } catch (e) {
144
+ }
145
+ }
146
+ return response;
147
+ };
148
+ return () => {
149
+ window.fetch = originalFetch;
150
+ };
151
+ }, []);
152
+ function doLogout() {
153
+ var _a;
154
+ (_a = window.google) == null ? void 0 : _a.accounts.id.disableAutoSelect();
155
+ deleteCookie(cookieName);
156
+ setUser(null);
157
+ setIsAdmin(false);
158
+ setIsEditing(false);
159
+ onLogout == null ? void 0 : onLogout();
160
+ }
161
+ const promptSignIn = (0, import_react.useCallback)(() => {
162
+ var _a;
163
+ if (!ready) return;
164
+ (_a = window.google) == null ? void 0 : _a.accounts.id.prompt();
165
+ }, [ready]);
166
+ const renderButton = (0, import_react.useCallback)(
167
+ (el, options) => {
168
+ var _a;
169
+ if (!ready) return;
170
+ (_a = window.google) == null ? void 0 : _a.accounts.id.renderButton(el, options);
171
+ },
172
+ [ready]
173
+ );
174
+ const toggleEdit = (0, import_react.useCallback)(() => setIsEditing((p) => !p), []);
175
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
176
+ GoogleAuthContext.Provider,
177
+ {
178
+ value: {
179
+ user,
180
+ isAdmin,
181
+ isEditing,
182
+ toggleEdit,
183
+ ready,
184
+ failed,
185
+ promptSignIn,
186
+ renderButton,
187
+ logout: doLogout
188
+ },
189
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_client.CmsAuthProvider, { value: { isAdmin, isEditing, toggleEdit }, children })
190
+ }
191
+ );
192
+ }
193
+ function useGoogleAuth() {
194
+ const ctx = (0, import_react.useContext)(GoogleAuthContext);
195
+ if (!ctx) {
196
+ throw new Error("useGoogleAuth must be used within a GoogleAuthProvider");
197
+ }
198
+ return ctx;
199
+ }
200
+ // Annotate the CommonJS export names for ESM import in node:
201
+ 0 && (module.exports = {
202
+ GoogleAuthProvider,
203
+ useGoogleAuth
204
+ });
205
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/auth/google/client/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n// Imported via the public specifier so we share the SAME context instance as\n// the consumer's edit primitives at runtime (see tsup `external` + tsconfig paths).\nimport { CmsAuthProvider } from \"@dalgoridim/headless-cms/client\";\n\n/**\n * Client provider for **Google Identity Services** sign-in — the Firebase-free\n * counterpart to `FirebaseAuthProvider`. It loads the GSI script, lets the user\n * sign in with Google, stashes the resulting ID token in a cookie for the\n * server `googleAuth` adapter to verify, and feeds the shared CMS auth context\n * so the edit primitives light up. Admin status is optimistic on the client\n * (via `adminEmails`); the server gate remains authoritative.\n */\n\nconst GSI_SRC = \"https://accounts.google.com/gsi/client\";\n\n/** Minimal slice of the Google Identity Services API we use. */\ninterface GsiCredentialResponse {\n credential: string;\n}\ninterface GsiButtonOptions {\n type?: \"standard\" | \"icon\";\n theme?: \"outline\" | \"filled_blue\" | \"filled_black\";\n size?: \"small\" | \"medium\" | \"large\";\n text?: \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\n shape?: \"rectangular\" | \"pill\" | \"circle\" | \"square\";\n width?: number;\n}\ninterface GsiClient {\n accounts: {\n id: {\n initialize: (config: {\n client_id: string;\n callback: (res: GsiCredentialResponse) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: () => void;\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n disableAutoSelect: () => void;\n };\n };\n}\n\ndeclare global {\n interface Window {\n google?: GsiClient;\n }\n}\n\nexport interface GoogleUser {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n}\n\nexport interface GoogleAuthProviderProps {\n children: ReactNode;\n /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */\n clientId: string;\n /**\n * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server\n * `googleAuth` adapter still enforces the real gate. Omit to treat any\n * successful sign-in as optimistically admin (server will correct via 401).\n */\n adminEmails?: string[];\n /** Cookie name for the ID token. Default `adminToken`. */\n cookieName?: string;\n /** Called when a 401 `{ logout: true }` response is intercepted. */\n onLogout?: () => void;\n}\n\nexport interface GoogleAuthContextValue {\n user: GoogleUser | null;\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n /** True once the GSI script has loaded and the client is initialized. */\n ready: boolean;\n /** True if GSI failed to load within the timeout (blocked/offline). */\n failed: boolean;\n /** Trigger Google One Tap / sign-in prompt. */\n promptSignIn: () => void;\n /** Render the official Google button into `el` (most reliable trigger). */\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n logout: () => void;\n}\n\nconst GoogleAuthContext = createContext<GoogleAuthContextValue | undefined>(\n undefined,\n);\n\nfunction setCookie(name: string, value: string) {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;\n}\nfunction deleteCookie(name: string) {\n document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\nfunction readCookie(name: string): string | null {\n for (const part of document.cookie.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Decode a JWT payload client-side (no verification — the server does that). */\nfunction decodeJwt(token: string): (GoogleUser & { exp?: number }) | null {\n try {\n const payload = token.split(\".\")[1];\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const data = JSON.parse(json) as {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n exp?: number;\n };\n return data;\n } catch {\n return null;\n }\n}\n\nexport function GoogleAuthProvider({\n children,\n clientId,\n adminEmails,\n cookieName = \"adminToken\",\n onLogout,\n}: GoogleAuthProviderProps) {\n const [user, setUser] = useState<GoogleUser | null>(null);\n const [isAdmin, setIsAdmin] = useState(false);\n const [isEditing, setIsEditing] = useState(false);\n const [ready, setReady] = useState(false);\n const [failed, setFailed] = useState(false);\n const allowed = useRef(\n (adminEmails ?? []).map((e) => e.trim().toLowerCase()),\n );\n\n const applyToken = useCallback(\n (token: string) => {\n const claims = decodeJwt(token);\n if (!claims) return;\n if (claims.exp && claims.exp * 1000 <= Date.now()) {\n deleteCookie(cookieName);\n return;\n }\n const email = claims.email?.toLowerCase();\n // Optimistic: if no allowlist supplied, trust the sign-in and let the\n // server correct a non-admin via the 401 interceptor below.\n const admin =\n allowed.current.length === 0\n ? true\n : !!email && allowed.current.includes(email);\n setCookie(cookieName, token);\n setUser({\n sub: claims.sub,\n email: claims.email,\n name: claims.name,\n picture: claims.picture,\n });\n setIsAdmin(admin);\n },\n [cookieName],\n );\n\n // Restore an existing session from the cookie on mount.\n useEffect(() => {\n const existing = readCookie(cookieName);\n if (existing) applyToken(existing);\n }, [cookieName, applyToken]);\n\n // Load the GSI script and initialize the client. Polling (rather than relying\n // on a `load` event) makes this robust against React's dev double-mount, a\n // cached/already-loaded script, and missed load events — any of which could\n // otherwise leave `ready` stuck false and the UI spinning forever.\n useEffect(() => {\n let cancelled = false;\n\n function tryInit(): boolean {\n if (cancelled || !window.google?.accounts?.id) return false;\n window.google.accounts.id.initialize({\n client_id: clientId,\n callback: (res) => applyToken(res.credential),\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n setReady(true);\n return true;\n }\n\n if (tryInit()) return;\n\n // Ensure the script tag exists exactly once across mounts.\n if (!document.querySelector(`script[src=\"${GSI_SRC}\"]`)) {\n const script = document.createElement(\"script\");\n script.src = GSI_SRC;\n script.async = true;\n script.defer = true;\n document.head.appendChild(script);\n }\n\n const startedAt = Date.now();\n const timer = setInterval(() => {\n if (tryInit()) {\n clearInterval(timer);\n } else if (Date.now() - startedAt > 10_000) {\n // Blocked (ad/privacy extension) or offline — surface a failure so the\n // UI can show a fallback instead of an endless spinner.\n clearInterval(timer);\n if (!cancelled) setFailed(true);\n }\n }, 120);\n\n return () => {\n cancelled = true;\n clearInterval(timer);\n };\n }, [clientId, applyToken]);\n\n // Intercept admin 401s so an expired/forbidden session forces sign-out.\n useEffect(() => {\n const originalFetch = window.fetch;\n window.fetch = async (...args: Parameters<typeof fetch>) => {\n const response = await originalFetch(...args);\n if (response.status === 401) {\n try {\n const data = await response.clone().json();\n if (data?.logout) doLogout();\n } catch {\n /* not a JSON body — ignore */\n }\n }\n return response;\n };\n return () => {\n window.fetch = originalFetch;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n function doLogout() {\n window.google?.accounts.id.disableAutoSelect();\n deleteCookie(cookieName);\n setUser(null);\n setIsAdmin(false);\n setIsEditing(false);\n onLogout?.();\n }\n\n const promptSignIn = useCallback(() => {\n if (!ready) return;\n window.google?.accounts.id.prompt();\n }, [ready]);\n\n const renderButton = useCallback(\n (el: HTMLElement, options?: GsiButtonOptions) => {\n if (!ready) return;\n window.google?.accounts.id.renderButton(el, options);\n },\n [ready],\n );\n\n const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);\n\n return (\n <GoogleAuthContext.Provider\n value={{\n user,\n isAdmin,\n isEditing,\n toggleEdit,\n ready,\n failed,\n promptSignIn,\n renderButton,\n logout: doLogout,\n }}\n >\n <CmsAuthProvider value={{ isAdmin, isEditing, toggleEdit }}>\n {children}\n </CmsAuthProvider>\n </GoogleAuthContext.Provider>\n );\n}\n\n/** Google auth API (user/login/logout) for login pages and toolbars. */\nexport function useGoogleAuth(): GoogleAuthContextValue {\n const ctx = useContext(GoogleAuthContext);\n if (!ctx) {\n throw new Error(\"useGoogleAuth must be used within a GoogleAuthProvider\");\n }\n return ctx;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAQO;AAGP,oBAAgC;AAsR1B;AA3QN,IAAM,UAAU;AA2EhB,IAAM,wBAAoB;AAAA,EACxB;AACF;AAEA,SAAS,UAAU,MAAc,OAAe;AAC9C,WAAS,SAAS,GAAG,IAAI,IAAI,mBAAmB,KAAK,CAAC;AACxD;AACA,SAAS,aAAa,MAAc;AAClC,WAAS,SAAS,GAAG,IAAI;AAC3B;AACA,SAAS,WAAW,MAA6B;AAC/C,aAAW,QAAQ,SAAS,OAAO,MAAM,GAAG,GAAG;AAC7C,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK,KAAK,EAAE,MAAM,GAAG;AACvC,QAAI,MAAM,KAAM,QAAO,mBAAmB,EAAE,KAAK,GAAG,CAAC;AAAA,EACvD;AACA,SAAO;AACT;AAGA,SAAS,UAAU,OAAuD;AACxE,MAAI;AACF,UAAM,UAAU,MAAM,MAAM,GAAG,EAAE,CAAC;AAClC,UAAM,OAAO,KAAK,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC;AAC/D,UAAM,OAAO,KAAK,MAAM,IAAI;AAO5B,WAAO;AAAA,EACT,SAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AACF,GAA4B;AAC1B,QAAM,CAAC,MAAM,OAAO,QAAI,uBAA4B,IAAI;AACxD,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,KAAK;AAC5C,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,KAAK;AACxC,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAS,KAAK;AAC1C,QAAM,cAAU;AAAA,KACb,oCAAe,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC;AAAA,EACvD;AAEA,QAAM,iBAAa;AAAA,IACjB,CAAC,UAAkB;AAxJvB;AAyJM,YAAM,SAAS,UAAU,KAAK;AAC9B,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,OAAO,OAAO,MAAM,OAAQ,KAAK,IAAI,GAAG;AACjD,qBAAa,UAAU;AACvB;AAAA,MACF;AACA,YAAM,SAAQ,YAAO,UAAP,mBAAc;AAG5B,YAAM,QACJ,QAAQ,QAAQ,WAAW,IACvB,OACA,CAAC,CAAC,SAAS,QAAQ,QAAQ,SAAS,KAAK;AAC/C,gBAAU,YAAY,KAAK;AAC3B,cAAQ;AAAA,QACN,KAAK,OAAO;AAAA,QACZ,OAAO,OAAO;AAAA,QACd,MAAM,OAAO;AAAA,QACb,SAAS,OAAO;AAAA,MAClB,CAAC;AACD,iBAAW,KAAK;AAAA,IAClB;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAGA,8BAAU,MAAM;AACd,UAAM,WAAW,WAAW,UAAU;AACtC,QAAI,SAAU,YAAW,QAAQ;AAAA,EACnC,GAAG,CAAC,YAAY,UAAU,CAAC;AAM3B,8BAAU,MAAM;AACd,QAAI,YAAY;AAEhB,aAAS,UAAmB;AA/LhC;AAgMM,UAAI,aAAa,GAAC,kBAAO,WAAP,mBAAe,aAAf,mBAAyB,IAAI,QAAO;AACtD,aAAO,OAAO,SAAS,GAAG,WAAW;AAAA,QACnC,WAAW;AAAA,QACX,UAAU,CAAC,QAAQ,WAAW,IAAI,UAAU;AAAA,QAC5C,aAAa;AAAA,QACb,uBAAuB;AAAA,MACzB,CAAC;AACD,eAAS,IAAI;AACb,aAAO;AAAA,IACT;AAEA,QAAI,QAAQ,EAAG;AAGf,QAAI,CAAC,SAAS,cAAc,eAAe,OAAO,IAAI,GAAG;AACvD,YAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,aAAO,MAAM;AACb,aAAO,QAAQ;AACf,aAAO,QAAQ;AACf,eAAS,KAAK,YAAY,MAAM;AAAA,IAClC;AAEA,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,QAAQ,YAAY,MAAM;AAC9B,UAAI,QAAQ,GAAG;AACb,sBAAc,KAAK;AAAA,MACrB,WAAW,KAAK,IAAI,IAAI,YAAY,KAAQ;AAG1C,sBAAc,KAAK;AACnB,YAAI,CAAC,UAAW,WAAU,IAAI;AAAA,MAChC;AAAA,IACF,GAAG,GAAG;AAEN,WAAO,MAAM;AACX,kBAAY;AACZ,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,UAAU,UAAU,CAAC;AAGzB,8BAAU,MAAM;AACd,UAAM,gBAAgB,OAAO;AAC7B,WAAO,QAAQ,UAAU,SAAmC;AAC1D,YAAM,WAAW,MAAM,cAAc,GAAG,IAAI;AAC5C,UAAI,SAAS,WAAW,KAAK;AAC3B,YAAI;AACF,gBAAM,OAAO,MAAM,SAAS,MAAM,EAAE,KAAK;AACzC,cAAI,6BAAM,OAAQ,UAAS;AAAA,QAC7B,SAAQ;AAAA,QAER;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,WAAO,MAAM;AACX,aAAO,QAAQ;AAAA,IACjB;AAAA,EAEF,GAAG,CAAC,CAAC;AAEL,WAAS,WAAW;AA7PtB;AA8PI,iBAAO,WAAP,mBAAe,SAAS,GAAG;AAC3B,iBAAa,UAAU;AACvB,YAAQ,IAAI;AACZ,eAAW,KAAK;AAChB,iBAAa,KAAK;AAClB;AAAA,EACF;AAEA,QAAM,mBAAe,0BAAY,MAAM;AAtQzC;AAuQI,QAAI,CAAC,MAAO;AACZ,iBAAO,WAAP,mBAAe,SAAS,GAAG;AAAA,EAC7B,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,mBAAe;AAAA,IACnB,CAAC,IAAiB,YAA+B;AA5QrD;AA6QM,UAAI,CAAC,MAAO;AACZ,mBAAO,WAAP,mBAAe,SAAS,GAAG,aAAa,IAAI;AAAA,IAC9C;AAAA,IACA,CAAC,KAAK;AAAA,EACR;AAEA,QAAM,iBAAa,0BAAY,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;AAEhE,SACE;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACC,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MACV;AAAA,MAEA,sDAAC,iCAAgB,OAAO,EAAE,SAAS,WAAW,WAAW,GACtD,UACH;AAAA;AAAA,EACF;AAEJ;AAGO,SAAS,gBAAwC;AACtD,QAAM,UAAM,yBAAW,iBAAiB;AACxC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,76 @@
1
+ import * as React from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ /** Minimal slice of the Google Identity Services API we use. */
5
+ interface GsiCredentialResponse {
6
+ credential: string;
7
+ }
8
+ interface GsiButtonOptions {
9
+ type?: "standard" | "icon";
10
+ theme?: "outline" | "filled_blue" | "filled_black";
11
+ size?: "small" | "medium" | "large";
12
+ text?: "signin_with" | "signup_with" | "continue_with" | "signin";
13
+ shape?: "rectangular" | "pill" | "circle" | "square";
14
+ width?: number;
15
+ }
16
+ interface GsiClient {
17
+ accounts: {
18
+ id: {
19
+ initialize: (config: {
20
+ client_id: string;
21
+ callback: (res: GsiCredentialResponse) => void;
22
+ auto_select?: boolean;
23
+ cancel_on_tap_outside?: boolean;
24
+ }) => void;
25
+ prompt: () => void;
26
+ renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
27
+ disableAutoSelect: () => void;
28
+ };
29
+ };
30
+ }
31
+ declare global {
32
+ interface Window {
33
+ google?: GsiClient;
34
+ }
35
+ }
36
+ interface GoogleUser {
37
+ sub: string;
38
+ email?: string;
39
+ name?: string;
40
+ picture?: string;
41
+ }
42
+ interface GoogleAuthProviderProps {
43
+ children: ReactNode;
44
+ /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */
45
+ clientId: string;
46
+ /**
47
+ * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server
48
+ * `googleAuth` adapter still enforces the real gate. Omit to treat any
49
+ * successful sign-in as optimistically admin (server will correct via 401).
50
+ */
51
+ adminEmails?: string[];
52
+ /** Cookie name for the ID token. Default `adminToken`. */
53
+ cookieName?: string;
54
+ /** Called when a 401 `{ logout: true }` response is intercepted. */
55
+ onLogout?: () => void;
56
+ }
57
+ interface GoogleAuthContextValue {
58
+ user: GoogleUser | null;
59
+ isAdmin: boolean;
60
+ isEditing: boolean;
61
+ toggleEdit: () => void;
62
+ /** True once the GSI script has loaded and the client is initialized. */
63
+ ready: boolean;
64
+ /** True if GSI failed to load within the timeout (blocked/offline). */
65
+ failed: boolean;
66
+ /** Trigger Google One Tap / sign-in prompt. */
67
+ promptSignIn: () => void;
68
+ /** Render the official Google button into `el` (most reliable trigger). */
69
+ renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
70
+ logout: () => void;
71
+ }
72
+ declare function GoogleAuthProvider({ children, clientId, adminEmails, cookieName, onLogout, }: GoogleAuthProviderProps): React.JSX.Element;
73
+ /** Google auth API (user/login/logout) for login pages and toolbars. */
74
+ declare function useGoogleAuth(): GoogleAuthContextValue;
75
+
76
+ export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, type GoogleUser, useGoogleAuth };
@@ -0,0 +1,76 @@
1
+ import * as React from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ /** Minimal slice of the Google Identity Services API we use. */
5
+ interface GsiCredentialResponse {
6
+ credential: string;
7
+ }
8
+ interface GsiButtonOptions {
9
+ type?: "standard" | "icon";
10
+ theme?: "outline" | "filled_blue" | "filled_black";
11
+ size?: "small" | "medium" | "large";
12
+ text?: "signin_with" | "signup_with" | "continue_with" | "signin";
13
+ shape?: "rectangular" | "pill" | "circle" | "square";
14
+ width?: number;
15
+ }
16
+ interface GsiClient {
17
+ accounts: {
18
+ id: {
19
+ initialize: (config: {
20
+ client_id: string;
21
+ callback: (res: GsiCredentialResponse) => void;
22
+ auto_select?: boolean;
23
+ cancel_on_tap_outside?: boolean;
24
+ }) => void;
25
+ prompt: () => void;
26
+ renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
27
+ disableAutoSelect: () => void;
28
+ };
29
+ };
30
+ }
31
+ declare global {
32
+ interface Window {
33
+ google?: GsiClient;
34
+ }
35
+ }
36
+ interface GoogleUser {
37
+ sub: string;
38
+ email?: string;
39
+ name?: string;
40
+ picture?: string;
41
+ }
42
+ interface GoogleAuthProviderProps {
43
+ children: ReactNode;
44
+ /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */
45
+ clientId: string;
46
+ /**
47
+ * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server
48
+ * `googleAuth` adapter still enforces the real gate. Omit to treat any
49
+ * successful sign-in as optimistically admin (server will correct via 401).
50
+ */
51
+ adminEmails?: string[];
52
+ /** Cookie name for the ID token. Default `adminToken`. */
53
+ cookieName?: string;
54
+ /** Called when a 401 `{ logout: true }` response is intercepted. */
55
+ onLogout?: () => void;
56
+ }
57
+ interface GoogleAuthContextValue {
58
+ user: GoogleUser | null;
59
+ isAdmin: boolean;
60
+ isEditing: boolean;
61
+ toggleEdit: () => void;
62
+ /** True once the GSI script has loaded and the client is initialized. */
63
+ ready: boolean;
64
+ /** True if GSI failed to load within the timeout (blocked/offline). */
65
+ failed: boolean;
66
+ /** Trigger Google One Tap / sign-in prompt. */
67
+ promptSignIn: () => void;
68
+ /** Render the official Google button into `el` (most reliable trigger). */
69
+ renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;
70
+ logout: () => void;
71
+ }
72
+ declare function GoogleAuthProvider({ children, clientId, adminEmails, cookieName, onLogout, }: GoogleAuthProviderProps): React.JSX.Element;
73
+ /** Google auth API (user/login/logout) for login pages and toolbars. */
74
+ declare function useGoogleAuth(): GoogleAuthContextValue;
75
+
76
+ export { type GoogleAuthContextValue, GoogleAuthProvider, type GoogleAuthProviderProps, type GoogleUser, useGoogleAuth };
@@ -0,0 +1,187 @@
1
+ "use client";
2
+
3
+ // src/auth/google/client/index.tsx
4
+ import {
5
+ createContext,
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useRef,
10
+ useState
11
+ } from "react";
12
+ import { CmsAuthProvider } from "@dalgoridim/headless-cms/client";
13
+ import { jsx } from "react/jsx-runtime";
14
+ var GSI_SRC = "https://accounts.google.com/gsi/client";
15
+ var GoogleAuthContext = createContext(
16
+ void 0
17
+ );
18
+ function setCookie(name, value) {
19
+ document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;
20
+ }
21
+ function deleteCookie(name) {
22
+ document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
23
+ }
24
+ function readCookie(name) {
25
+ for (const part of document.cookie.split(";")) {
26
+ const [k, ...v] = part.trim().split("=");
27
+ if (k === name) return decodeURIComponent(v.join("="));
28
+ }
29
+ return null;
30
+ }
31
+ function decodeJwt(token) {
32
+ try {
33
+ const payload = token.split(".")[1];
34
+ const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
35
+ const data = JSON.parse(json);
36
+ return data;
37
+ } catch (e) {
38
+ return null;
39
+ }
40
+ }
41
+ function GoogleAuthProvider({
42
+ children,
43
+ clientId,
44
+ adminEmails,
45
+ cookieName = "adminToken",
46
+ onLogout
47
+ }) {
48
+ const [user, setUser] = useState(null);
49
+ const [isAdmin, setIsAdmin] = useState(false);
50
+ const [isEditing, setIsEditing] = useState(false);
51
+ const [ready, setReady] = useState(false);
52
+ const [failed, setFailed] = useState(false);
53
+ const allowed = useRef(
54
+ (adminEmails != null ? adminEmails : []).map((e) => e.trim().toLowerCase())
55
+ );
56
+ const applyToken = useCallback(
57
+ (token) => {
58
+ var _a;
59
+ const claims = decodeJwt(token);
60
+ if (!claims) return;
61
+ if (claims.exp && claims.exp * 1e3 <= Date.now()) {
62
+ deleteCookie(cookieName);
63
+ return;
64
+ }
65
+ const email = (_a = claims.email) == null ? void 0 : _a.toLowerCase();
66
+ const admin = allowed.current.length === 0 ? true : !!email && allowed.current.includes(email);
67
+ setCookie(cookieName, token);
68
+ setUser({
69
+ sub: claims.sub,
70
+ email: claims.email,
71
+ name: claims.name,
72
+ picture: claims.picture
73
+ });
74
+ setIsAdmin(admin);
75
+ },
76
+ [cookieName]
77
+ );
78
+ useEffect(() => {
79
+ const existing = readCookie(cookieName);
80
+ if (existing) applyToken(existing);
81
+ }, [cookieName, applyToken]);
82
+ useEffect(() => {
83
+ let cancelled = false;
84
+ function tryInit() {
85
+ var _a, _b;
86
+ if (cancelled || !((_b = (_a = window.google) == null ? void 0 : _a.accounts) == null ? void 0 : _b.id)) return false;
87
+ window.google.accounts.id.initialize({
88
+ client_id: clientId,
89
+ callback: (res) => applyToken(res.credential),
90
+ auto_select: false,
91
+ cancel_on_tap_outside: true
92
+ });
93
+ setReady(true);
94
+ return true;
95
+ }
96
+ if (tryInit()) return;
97
+ if (!document.querySelector(`script[src="${GSI_SRC}"]`)) {
98
+ const script = document.createElement("script");
99
+ script.src = GSI_SRC;
100
+ script.async = true;
101
+ script.defer = true;
102
+ document.head.appendChild(script);
103
+ }
104
+ const startedAt = Date.now();
105
+ const timer = setInterval(() => {
106
+ if (tryInit()) {
107
+ clearInterval(timer);
108
+ } else if (Date.now() - startedAt > 1e4) {
109
+ clearInterval(timer);
110
+ if (!cancelled) setFailed(true);
111
+ }
112
+ }, 120);
113
+ return () => {
114
+ cancelled = true;
115
+ clearInterval(timer);
116
+ };
117
+ }, [clientId, applyToken]);
118
+ useEffect(() => {
119
+ const originalFetch = window.fetch;
120
+ window.fetch = async (...args) => {
121
+ const response = await originalFetch(...args);
122
+ if (response.status === 401) {
123
+ try {
124
+ const data = await response.clone().json();
125
+ if (data == null ? void 0 : data.logout) doLogout();
126
+ } catch (e) {
127
+ }
128
+ }
129
+ return response;
130
+ };
131
+ return () => {
132
+ window.fetch = originalFetch;
133
+ };
134
+ }, []);
135
+ function doLogout() {
136
+ var _a;
137
+ (_a = window.google) == null ? void 0 : _a.accounts.id.disableAutoSelect();
138
+ deleteCookie(cookieName);
139
+ setUser(null);
140
+ setIsAdmin(false);
141
+ setIsEditing(false);
142
+ onLogout == null ? void 0 : onLogout();
143
+ }
144
+ const promptSignIn = useCallback(() => {
145
+ var _a;
146
+ if (!ready) return;
147
+ (_a = window.google) == null ? void 0 : _a.accounts.id.prompt();
148
+ }, [ready]);
149
+ const renderButton = useCallback(
150
+ (el, options) => {
151
+ var _a;
152
+ if (!ready) return;
153
+ (_a = window.google) == null ? void 0 : _a.accounts.id.renderButton(el, options);
154
+ },
155
+ [ready]
156
+ );
157
+ const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);
158
+ return /* @__PURE__ */ jsx(
159
+ GoogleAuthContext.Provider,
160
+ {
161
+ value: {
162
+ user,
163
+ isAdmin,
164
+ isEditing,
165
+ toggleEdit,
166
+ ready,
167
+ failed,
168
+ promptSignIn,
169
+ renderButton,
170
+ logout: doLogout
171
+ },
172
+ children: /* @__PURE__ */ jsx(CmsAuthProvider, { value: { isAdmin, isEditing, toggleEdit }, children })
173
+ }
174
+ );
175
+ }
176
+ function useGoogleAuth() {
177
+ const ctx = useContext(GoogleAuthContext);
178
+ if (!ctx) {
179
+ throw new Error("useGoogleAuth must be used within a GoogleAuthProvider");
180
+ }
181
+ return ctx;
182
+ }
183
+ export {
184
+ GoogleAuthProvider,
185
+ useGoogleAuth
186
+ };
187
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/auth/google/client/index.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n// Imported via the public specifier so we share the SAME context instance as\n// the consumer's edit primitives at runtime (see tsup `external` + tsconfig paths).\nimport { CmsAuthProvider } from \"@dalgoridim/headless-cms/client\";\n\n/**\n * Client provider for **Google Identity Services** sign-in — the Firebase-free\n * counterpart to `FirebaseAuthProvider`. It loads the GSI script, lets the user\n * sign in with Google, stashes the resulting ID token in a cookie for the\n * server `googleAuth` adapter to verify, and feeds the shared CMS auth context\n * so the edit primitives light up. Admin status is optimistic on the client\n * (via `adminEmails`); the server gate remains authoritative.\n */\n\nconst GSI_SRC = \"https://accounts.google.com/gsi/client\";\n\n/** Minimal slice of the Google Identity Services API we use. */\ninterface GsiCredentialResponse {\n credential: string;\n}\ninterface GsiButtonOptions {\n type?: \"standard\" | \"icon\";\n theme?: \"outline\" | \"filled_blue\" | \"filled_black\";\n size?: \"small\" | \"medium\" | \"large\";\n text?: \"signin_with\" | \"signup_with\" | \"continue_with\" | \"signin\";\n shape?: \"rectangular\" | \"pill\" | \"circle\" | \"square\";\n width?: number;\n}\ninterface GsiClient {\n accounts: {\n id: {\n initialize: (config: {\n client_id: string;\n callback: (res: GsiCredentialResponse) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: () => void;\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n disableAutoSelect: () => void;\n };\n };\n}\n\ndeclare global {\n interface Window {\n google?: GsiClient;\n }\n}\n\nexport interface GoogleUser {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n}\n\nexport interface GoogleAuthProviderProps {\n children: ReactNode;\n /** OAuth 2.0 Web client ID (from Google Cloud Console → Credentials). */\n clientId: string;\n /**\n * Optional admin allowlist for *optimistic* client-side `isAdmin`. The server\n * `googleAuth` adapter still enforces the real gate. Omit to treat any\n * successful sign-in as optimistically admin (server will correct via 401).\n */\n adminEmails?: string[];\n /** Cookie name for the ID token. Default `adminToken`. */\n cookieName?: string;\n /** Called when a 401 `{ logout: true }` response is intercepted. */\n onLogout?: () => void;\n}\n\nexport interface GoogleAuthContextValue {\n user: GoogleUser | null;\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n /** True once the GSI script has loaded and the client is initialized. */\n ready: boolean;\n /** True if GSI failed to load within the timeout (blocked/offline). */\n failed: boolean;\n /** Trigger Google One Tap / sign-in prompt. */\n promptSignIn: () => void;\n /** Render the official Google button into `el` (most reliable trigger). */\n renderButton: (el: HTMLElement, options?: GsiButtonOptions) => void;\n logout: () => void;\n}\n\nconst GoogleAuthContext = createContext<GoogleAuthContextValue | undefined>(\n undefined,\n);\n\nfunction setCookie(name: string, value: string) {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; samesite=lax`;\n}\nfunction deleteCookie(name: string) {\n document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;\n}\nfunction readCookie(name: string): string | null {\n for (const part of document.cookie.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Decode a JWT payload client-side (no verification — the server does that). */\nfunction decodeJwt(token: string): (GoogleUser & { exp?: number }) | null {\n try {\n const payload = token.split(\".\")[1];\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const data = JSON.parse(json) as {\n sub: string;\n email?: string;\n name?: string;\n picture?: string;\n exp?: number;\n };\n return data;\n } catch {\n return null;\n }\n}\n\nexport function GoogleAuthProvider({\n children,\n clientId,\n adminEmails,\n cookieName = \"adminToken\",\n onLogout,\n}: GoogleAuthProviderProps) {\n const [user, setUser] = useState<GoogleUser | null>(null);\n const [isAdmin, setIsAdmin] = useState(false);\n const [isEditing, setIsEditing] = useState(false);\n const [ready, setReady] = useState(false);\n const [failed, setFailed] = useState(false);\n const allowed = useRef(\n (adminEmails ?? []).map((e) => e.trim().toLowerCase()),\n );\n\n const applyToken = useCallback(\n (token: string) => {\n const claims = decodeJwt(token);\n if (!claims) return;\n if (claims.exp && claims.exp * 1000 <= Date.now()) {\n deleteCookie(cookieName);\n return;\n }\n const email = claims.email?.toLowerCase();\n // Optimistic: if no allowlist supplied, trust the sign-in and let the\n // server correct a non-admin via the 401 interceptor below.\n const admin =\n allowed.current.length === 0\n ? true\n : !!email && allowed.current.includes(email);\n setCookie(cookieName, token);\n setUser({\n sub: claims.sub,\n email: claims.email,\n name: claims.name,\n picture: claims.picture,\n });\n setIsAdmin(admin);\n },\n [cookieName],\n );\n\n // Restore an existing session from the cookie on mount.\n useEffect(() => {\n const existing = readCookie(cookieName);\n if (existing) applyToken(existing);\n }, [cookieName, applyToken]);\n\n // Load the GSI script and initialize the client. Polling (rather than relying\n // on a `load` event) makes this robust against React's dev double-mount, a\n // cached/already-loaded script, and missed load events — any of which could\n // otherwise leave `ready` stuck false and the UI spinning forever.\n useEffect(() => {\n let cancelled = false;\n\n function tryInit(): boolean {\n if (cancelled || !window.google?.accounts?.id) return false;\n window.google.accounts.id.initialize({\n client_id: clientId,\n callback: (res) => applyToken(res.credential),\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n setReady(true);\n return true;\n }\n\n if (tryInit()) return;\n\n // Ensure the script tag exists exactly once across mounts.\n if (!document.querySelector(`script[src=\"${GSI_SRC}\"]`)) {\n const script = document.createElement(\"script\");\n script.src = GSI_SRC;\n script.async = true;\n script.defer = true;\n document.head.appendChild(script);\n }\n\n const startedAt = Date.now();\n const timer = setInterval(() => {\n if (tryInit()) {\n clearInterval(timer);\n } else if (Date.now() - startedAt > 10_000) {\n // Blocked (ad/privacy extension) or offline — surface a failure so the\n // UI can show a fallback instead of an endless spinner.\n clearInterval(timer);\n if (!cancelled) setFailed(true);\n }\n }, 120);\n\n return () => {\n cancelled = true;\n clearInterval(timer);\n };\n }, [clientId, applyToken]);\n\n // Intercept admin 401s so an expired/forbidden session forces sign-out.\n useEffect(() => {\n const originalFetch = window.fetch;\n window.fetch = async (...args: Parameters<typeof fetch>) => {\n const response = await originalFetch(...args);\n if (response.status === 401) {\n try {\n const data = await response.clone().json();\n if (data?.logout) doLogout();\n } catch {\n /* not a JSON body — ignore */\n }\n }\n return response;\n };\n return () => {\n window.fetch = originalFetch;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n function doLogout() {\n window.google?.accounts.id.disableAutoSelect();\n deleteCookie(cookieName);\n setUser(null);\n setIsAdmin(false);\n setIsEditing(false);\n onLogout?.();\n }\n\n const promptSignIn = useCallback(() => {\n if (!ready) return;\n window.google?.accounts.id.prompt();\n }, [ready]);\n\n const renderButton = useCallback(\n (el: HTMLElement, options?: GsiButtonOptions) => {\n if (!ready) return;\n window.google?.accounts.id.renderButton(el, options);\n },\n [ready],\n );\n\n const toggleEdit = useCallback(() => setIsEditing((p) => !p), []);\n\n return (\n <GoogleAuthContext.Provider\n value={{\n user,\n isAdmin,\n isEditing,\n toggleEdit,\n ready,\n failed,\n promptSignIn,\n renderButton,\n logout: doLogout,\n }}\n >\n <CmsAuthProvider value={{ isAdmin, isEditing, toggleEdit }}>\n {children}\n </CmsAuthProvider>\n </GoogleAuthContext.Provider>\n );\n}\n\n/** Google auth API (user/login/logout) for login pages and toolbars. */\nexport function useGoogleAuth(): GoogleAuthContextValue {\n const ctx = useContext(GoogleAuthContext);\n if (!ctx) {\n throw new Error(\"useGoogleAuth must be used within a GoogleAuthProvider\");\n }\n return ctx;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/auth/google/index.ts
21
+ var google_exports = {};
22
+ __export(google_exports, {
23
+ googleAuth: () => googleAuth,
24
+ verifyGoogleIdToken: () => verifyGoogleIdToken
25
+ });
26
+ module.exports = __toCommonJS(google_exports);
27
+ var import_node_crypto = require("crypto");
28
+ var GOOGLE_CERTS_URL = "https://www.googleapis.com/oauth2/v3/certs";
29
+ var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
30
+ var keyCache = null;
31
+ async function getSigningKey(kid) {
32
+ var _a, _b;
33
+ const now = Date.now();
34
+ if (!keyCache || now >= keyCache.expiresAt) {
35
+ const res = await fetch(GOOGLE_CERTS_URL);
36
+ if (!res.ok) throw new Error(`Google JWKS fetch failed: ${res.status}`);
37
+ const body = await res.json();
38
+ const keys = new Map(body.keys.map((k) => [k.kid, k]));
39
+ const maxAge = /max-age=(\d+)/.exec((_a = res.headers.get("cache-control")) != null ? _a : "");
40
+ keyCache = {
41
+ keys,
42
+ expiresAt: now + (maxAge ? Number(maxAge[1]) * 1e3 : 36e5)
43
+ };
44
+ }
45
+ return (_b = keyCache.keys.get(kid)) != null ? _b : null;
46
+ }
47
+ function b64urlToBuffer(input) {
48
+ return Buffer.from(input.replace(/-/g, "+").replace(/_/g, "/"), "base64");
49
+ }
50
+ function b64urlToJson(input) {
51
+ return JSON.parse(b64urlToBuffer(input).toString("utf8"));
52
+ }
53
+ function readCookie(req, name) {
54
+ const header = req.headers.get("cookie");
55
+ if (!header) return null;
56
+ for (const part of header.split(";")) {
57
+ const [k, ...v] = part.trim().split("=");
58
+ if (k === name) return decodeURIComponent(v.join("="));
59
+ }
60
+ return null;
61
+ }
62
+ async function verifyGoogleIdToken(token, opts) {
63
+ var _a, _b, _c;
64
+ const parts = token.split(".");
65
+ if (parts.length !== 3) throw new Error("Malformed ID token");
66
+ const [headerB64, payloadB64, signatureB64] = parts;
67
+ const header = b64urlToJson(headerB64);
68
+ if (header.alg !== "RS256") throw new Error(`Unexpected alg: ${header.alg}`);
69
+ const jwk = await getSigningKey(header.kid);
70
+ if (!jwk) throw new Error("No matching Google signing key for token");
71
+ const publicKey = (0, import_node_crypto.createPublicKey)({
72
+ key: jwk,
73
+ format: "jwk"
74
+ });
75
+ const verifier = (0, import_node_crypto.createVerify)("RSA-SHA256");
76
+ verifier.update(`${headerB64}.${payloadB64}`);
77
+ verifier.end();
78
+ if (!verifier.verify(publicKey, b64urlToBuffer(signatureB64))) {
79
+ throw new Error("Invalid ID token signature");
80
+ }
81
+ const payload = b64urlToJson(payloadB64);
82
+ const nowSec = Math.floor(((_b = (_a = opts.now) == null ? void 0 : _a.call(opts)) != null ? _b : Date.now()) / 1e3);
83
+ if (payload.exp <= nowSec) throw new Error("ID token expired");
84
+ const issuers = (_c = opts.issuers) != null ? _c : GOOGLE_ISSUERS;
85
+ if (!issuers.includes(payload.iss)) {
86
+ throw new Error(`Untrusted issuer: ${payload.iss}`);
87
+ }
88
+ const audiences = Array.isArray(opts.clientId) ? opts.clientId : [opts.clientId];
89
+ if (!audiences.includes(payload.aud)) throw new Error("Audience mismatch");
90
+ return payload;
91
+ }
92
+ function googleAuth(config) {
93
+ var _a;
94
+ const cookieName = (_a = config.cookieName) != null ? _a : "adminToken";
95
+ const allowed = config.adminEmails.map((e) => e.trim().toLowerCase());
96
+ return {
97
+ async verifyRequest(req) {
98
+ var _a2;
99
+ const token = readCookie(req, cookieName);
100
+ if (!token) return null;
101
+ let payload;
102
+ try {
103
+ payload = await verifyGoogleIdToken(token, {
104
+ clientId: config.clientId,
105
+ issuers: config.issuers
106
+ });
107
+ } catch (e) {
108
+ return null;
109
+ }
110
+ const email = (_a2 = payload.email) == null ? void 0 : _a2.toLowerCase();
111
+ const isAdmin = !!email && payload.email_verified === true && allowed.includes(email);
112
+ return { userId: payload.sub, email: payload.email, isAdmin };
113
+ }
114
+ };
115
+ }
116
+ // Annotate the CommonJS export names for ESM import in node:
117
+ 0 && (module.exports = {
118
+ googleAuth,
119
+ verifyGoogleIdToken
120
+ });
121
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/auth/google/index.ts"],"sourcesContent":["import { createPublicKey, createVerify, type JsonWebKey } from \"node:crypto\";\nimport type { AuthAdapter, AuthIdentity } from \"../../types\";\n\n/**\n * Server auth adapter backed by **Google Identity Services** ID tokens — the\n * lightweight alternative to the Firebase adapter when all you want is \"Sign in\n * with Google\". No Firebase project, no service account: the browser obtains a\n * signed Google ID token (a JWT), drops it in a cookie, and this adapter\n * verifies it locally against Google's public keys, then gates on an email\n * allowlist.\n *\n * Verification is dependency-free (Node's built-in `crypto` + `fetch`), so the\n * package keeps its zero-runtime-deps contract.\n */\n\nconst GOOGLE_CERTS_URL = \"https://www.googleapis.com/oauth2/v3/certs\";\nconst GOOGLE_ISSUERS = [\"https://accounts.google.com\", \"accounts.google.com\"];\n\n/** A Google JWKS entry (RSA public key in JWK form). */\ninterface GoogleJwk {\n kid: string;\n kty: string;\n n: string;\n e: string;\n alg?: string;\n use?: string;\n}\n\nlet keyCache: { keys: Map<string, GoogleJwk>; expiresAt: number } | null = null;\n\n/** Fetch (and cache, honoring `cache-control: max-age`) Google's signing keys. */\nasync function getSigningKey(kid: string): Promise<GoogleJwk | null> {\n const now = Date.now();\n if (!keyCache || now >= keyCache.expiresAt) {\n const res = await fetch(GOOGLE_CERTS_URL);\n if (!res.ok) throw new Error(`Google JWKS fetch failed: ${res.status}`);\n const body = (await res.json()) as { keys: GoogleJwk[] };\n const keys = new Map(body.keys.map((k) => [k.kid, k]));\n const maxAge = /max-age=(\\d+)/.exec(res.headers.get(\"cache-control\") ?? \"\");\n keyCache = {\n keys,\n expiresAt: now + (maxAge ? Number(maxAge[1]) * 1000 : 3_600_000),\n };\n }\n return keyCache.keys.get(kid) ?? null;\n}\n\nfunction b64urlToBuffer(input: string): Buffer {\n return Buffer.from(input.replace(/-/g, \"+\").replace(/_/g, \"/\"), \"base64\");\n}\n\nfunction b64urlToJson<T>(input: string): T {\n return JSON.parse(b64urlToBuffer(input).toString(\"utf8\")) as T;\n}\n\nfunction readCookie(req: Request, name: string): string | null {\n const header = req.headers.get(\"cookie\");\n if (!header) return null;\n for (const part of header.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Claims of interest from a verified Google ID token. */\nexport interface GoogleIdTokenPayload {\n iss: string;\n aud: string;\n sub: string;\n exp: number;\n iat: number;\n email?: string;\n email_verified?: boolean;\n name?: string;\n picture?: string;\n}\n\nexport interface VerifyGoogleIdTokenOptions {\n /** Expected audience — your OAuth 2.0 Web client ID(s). */\n clientId: string | string[];\n /** Accepted issuers. Defaults to Google's two canonical issuers. */\n issuers?: string[];\n /** Clock injection for tests. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/**\n * Verify a Google ID token end-to-end: RS256 signature against Google's JWKS,\n * plus `exp` / `iss` / `aud` checks. Throws on any failure; resolves with the\n * decoded payload on success. Exported standalone for custom flows/tests.\n */\nexport async function verifyGoogleIdToken(\n token: string,\n opts: VerifyGoogleIdTokenOptions,\n): Promise<GoogleIdTokenPayload> {\n const parts = token.split(\".\");\n if (parts.length !== 3) throw new Error(\"Malformed ID token\");\n const [headerB64, payloadB64, signatureB64] = parts;\n\n const header = b64urlToJson<{ alg: string; kid: string }>(headerB64);\n if (header.alg !== \"RS256\") throw new Error(`Unexpected alg: ${header.alg}`);\n\n const jwk = await getSigningKey(header.kid);\n if (!jwk) throw new Error(\"No matching Google signing key for token\");\n\n const publicKey = createPublicKey({\n key: jwk as unknown as JsonWebKey,\n format: \"jwk\",\n });\n const verifier = createVerify(\"RSA-SHA256\");\n verifier.update(`${headerB64}.${payloadB64}`);\n verifier.end();\n if (!verifier.verify(publicKey, b64urlToBuffer(signatureB64))) {\n throw new Error(\"Invalid ID token signature\");\n }\n\n const payload = b64urlToJson<GoogleIdTokenPayload>(payloadB64);\n const nowSec = Math.floor((opts.now?.() ?? Date.now()) / 1000);\n if (payload.exp <= nowSec) throw new Error(\"ID token expired\");\n\n const issuers = opts.issuers ?? GOOGLE_ISSUERS;\n if (!issuers.includes(payload.iss)) {\n throw new Error(`Untrusted issuer: ${payload.iss}`);\n }\n const audiences = Array.isArray(opts.clientId)\n ? opts.clientId\n : [opts.clientId];\n if (!audiences.includes(payload.aud)) throw new Error(\"Audience mismatch\");\n\n return payload;\n}\n\nexport interface GoogleAuthConfig {\n /** OAuth 2.0 Web client ID(s); the token's `aud` must match one of these. */\n clientId: string | string[];\n /** Allowlist of admin emails (compared case-insensitively). */\n adminEmails: string[];\n /** Cookie carrying the Google ID token. Default `adminToken`. */\n cookieName?: string;\n /** Accepted issuers (override for testing). */\n issuers?: string[];\n}\n\n/**\n * Build an {@link AuthAdapter} that verifies the Google ID token from the\n * request cookie and grants admin only to a verified email in `adminEmails`.\n */\nexport function googleAuth(config: GoogleAuthConfig): AuthAdapter {\n const cookieName = config.cookieName ?? \"adminToken\";\n const allowed = config.adminEmails.map((e) => e.trim().toLowerCase());\n\n return {\n async verifyRequest(req: Request): Promise<AuthIdentity | null> {\n const token = readCookie(req, cookieName);\n if (!token) return null;\n\n let payload: GoogleIdTokenPayload;\n try {\n payload = await verifyGoogleIdToken(token, {\n clientId: config.clientId,\n issuers: config.issuers,\n });\n } catch {\n // Expired/invalid/tampered → unauthorized, so the gate emits a\n // 401 { logout: true } and the client can force sign-out.\n return null;\n }\n\n const email = payload.email?.toLowerCase();\n const isAdmin =\n !!email && payload.email_verified === true && allowed.includes(email);\n\n return { userId: payload.sub, email: payload.email, isAdmin };\n },\n };\n}\n"],"mappings":"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","names":["_a"]}
@@ -0,0 +1,45 @@
1
+ import { AuthAdapter } from '../../index.cjs';
2
+
3
+ /** Claims of interest from a verified Google ID token. */
4
+ interface GoogleIdTokenPayload {
5
+ iss: string;
6
+ aud: string;
7
+ sub: string;
8
+ exp: number;
9
+ iat: number;
10
+ email?: string;
11
+ email_verified?: boolean;
12
+ name?: string;
13
+ picture?: string;
14
+ }
15
+ interface VerifyGoogleIdTokenOptions {
16
+ /** Expected audience — your OAuth 2.0 Web client ID(s). */
17
+ clientId: string | string[];
18
+ /** Accepted issuers. Defaults to Google's two canonical issuers. */
19
+ issuers?: string[];
20
+ /** Clock injection for tests. Defaults to `Date.now`. */
21
+ now?: () => number;
22
+ }
23
+ /**
24
+ * Verify a Google ID token end-to-end: RS256 signature against Google's JWKS,
25
+ * plus `exp` / `iss` / `aud` checks. Throws on any failure; resolves with the
26
+ * decoded payload on success. Exported standalone for custom flows/tests.
27
+ */
28
+ declare function verifyGoogleIdToken(token: string, opts: VerifyGoogleIdTokenOptions): Promise<GoogleIdTokenPayload>;
29
+ interface GoogleAuthConfig {
30
+ /** OAuth 2.0 Web client ID(s); the token's `aud` must match one of these. */
31
+ clientId: string | string[];
32
+ /** Allowlist of admin emails (compared case-insensitively). */
33
+ adminEmails: string[];
34
+ /** Cookie carrying the Google ID token. Default `adminToken`. */
35
+ cookieName?: string;
36
+ /** Accepted issuers (override for testing). */
37
+ issuers?: string[];
38
+ }
39
+ /**
40
+ * Build an {@link AuthAdapter} that verifies the Google ID token from the
41
+ * request cookie and grants admin only to a verified email in `adminEmails`.
42
+ */
43
+ declare function googleAuth(config: GoogleAuthConfig): AuthAdapter;
44
+
45
+ export { type GoogleAuthConfig, type GoogleIdTokenPayload, type VerifyGoogleIdTokenOptions, googleAuth, verifyGoogleIdToken };
@@ -0,0 +1,45 @@
1
+ import { AuthAdapter } from '../../index.js';
2
+
3
+ /** Claims of interest from a verified Google ID token. */
4
+ interface GoogleIdTokenPayload {
5
+ iss: string;
6
+ aud: string;
7
+ sub: string;
8
+ exp: number;
9
+ iat: number;
10
+ email?: string;
11
+ email_verified?: boolean;
12
+ name?: string;
13
+ picture?: string;
14
+ }
15
+ interface VerifyGoogleIdTokenOptions {
16
+ /** Expected audience — your OAuth 2.0 Web client ID(s). */
17
+ clientId: string | string[];
18
+ /** Accepted issuers. Defaults to Google's two canonical issuers. */
19
+ issuers?: string[];
20
+ /** Clock injection for tests. Defaults to `Date.now`. */
21
+ now?: () => number;
22
+ }
23
+ /**
24
+ * Verify a Google ID token end-to-end: RS256 signature against Google's JWKS,
25
+ * plus `exp` / `iss` / `aud` checks. Throws on any failure; resolves with the
26
+ * decoded payload on success. Exported standalone for custom flows/tests.
27
+ */
28
+ declare function verifyGoogleIdToken(token: string, opts: VerifyGoogleIdTokenOptions): Promise<GoogleIdTokenPayload>;
29
+ interface GoogleAuthConfig {
30
+ /** OAuth 2.0 Web client ID(s); the token's `aud` must match one of these. */
31
+ clientId: string | string[];
32
+ /** Allowlist of admin emails (compared case-insensitively). */
33
+ adminEmails: string[];
34
+ /** Cookie carrying the Google ID token. Default `adminToken`. */
35
+ cookieName?: string;
36
+ /** Accepted issuers (override for testing). */
37
+ issuers?: string[];
38
+ }
39
+ /**
40
+ * Build an {@link AuthAdapter} that verifies the Google ID token from the
41
+ * request cookie and grants admin only to a verified email in `adminEmails`.
42
+ */
43
+ declare function googleAuth(config: GoogleAuthConfig): AuthAdapter;
44
+
45
+ export { type GoogleAuthConfig, type GoogleIdTokenPayload, type VerifyGoogleIdTokenOptions, googleAuth, verifyGoogleIdToken };
@@ -0,0 +1,95 @@
1
+ // src/auth/google/index.ts
2
+ import { createPublicKey, createVerify } from "crypto";
3
+ var GOOGLE_CERTS_URL = "https://www.googleapis.com/oauth2/v3/certs";
4
+ var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
5
+ var keyCache = null;
6
+ async function getSigningKey(kid) {
7
+ var _a, _b;
8
+ const now = Date.now();
9
+ if (!keyCache || now >= keyCache.expiresAt) {
10
+ const res = await fetch(GOOGLE_CERTS_URL);
11
+ if (!res.ok) throw new Error(`Google JWKS fetch failed: ${res.status}`);
12
+ const body = await res.json();
13
+ const keys = new Map(body.keys.map((k) => [k.kid, k]));
14
+ const maxAge = /max-age=(\d+)/.exec((_a = res.headers.get("cache-control")) != null ? _a : "");
15
+ keyCache = {
16
+ keys,
17
+ expiresAt: now + (maxAge ? Number(maxAge[1]) * 1e3 : 36e5)
18
+ };
19
+ }
20
+ return (_b = keyCache.keys.get(kid)) != null ? _b : null;
21
+ }
22
+ function b64urlToBuffer(input) {
23
+ return Buffer.from(input.replace(/-/g, "+").replace(/_/g, "/"), "base64");
24
+ }
25
+ function b64urlToJson(input) {
26
+ return JSON.parse(b64urlToBuffer(input).toString("utf8"));
27
+ }
28
+ function readCookie(req, name) {
29
+ const header = req.headers.get("cookie");
30
+ if (!header) return null;
31
+ for (const part of header.split(";")) {
32
+ const [k, ...v] = part.trim().split("=");
33
+ if (k === name) return decodeURIComponent(v.join("="));
34
+ }
35
+ return null;
36
+ }
37
+ async function verifyGoogleIdToken(token, opts) {
38
+ var _a, _b, _c;
39
+ const parts = token.split(".");
40
+ if (parts.length !== 3) throw new Error("Malformed ID token");
41
+ const [headerB64, payloadB64, signatureB64] = parts;
42
+ const header = b64urlToJson(headerB64);
43
+ if (header.alg !== "RS256") throw new Error(`Unexpected alg: ${header.alg}`);
44
+ const jwk = await getSigningKey(header.kid);
45
+ if (!jwk) throw new Error("No matching Google signing key for token");
46
+ const publicKey = createPublicKey({
47
+ key: jwk,
48
+ format: "jwk"
49
+ });
50
+ const verifier = createVerify("RSA-SHA256");
51
+ verifier.update(`${headerB64}.${payloadB64}`);
52
+ verifier.end();
53
+ if (!verifier.verify(publicKey, b64urlToBuffer(signatureB64))) {
54
+ throw new Error("Invalid ID token signature");
55
+ }
56
+ const payload = b64urlToJson(payloadB64);
57
+ const nowSec = Math.floor(((_b = (_a = opts.now) == null ? void 0 : _a.call(opts)) != null ? _b : Date.now()) / 1e3);
58
+ if (payload.exp <= nowSec) throw new Error("ID token expired");
59
+ const issuers = (_c = opts.issuers) != null ? _c : GOOGLE_ISSUERS;
60
+ if (!issuers.includes(payload.iss)) {
61
+ throw new Error(`Untrusted issuer: ${payload.iss}`);
62
+ }
63
+ const audiences = Array.isArray(opts.clientId) ? opts.clientId : [opts.clientId];
64
+ if (!audiences.includes(payload.aud)) throw new Error("Audience mismatch");
65
+ return payload;
66
+ }
67
+ function googleAuth(config) {
68
+ var _a;
69
+ const cookieName = (_a = config.cookieName) != null ? _a : "adminToken";
70
+ const allowed = config.adminEmails.map((e) => e.trim().toLowerCase());
71
+ return {
72
+ async verifyRequest(req) {
73
+ var _a2;
74
+ const token = readCookie(req, cookieName);
75
+ if (!token) return null;
76
+ let payload;
77
+ try {
78
+ payload = await verifyGoogleIdToken(token, {
79
+ clientId: config.clientId,
80
+ issuers: config.issuers
81
+ });
82
+ } catch (e) {
83
+ return null;
84
+ }
85
+ const email = (_a2 = payload.email) == null ? void 0 : _a2.toLowerCase();
86
+ const isAdmin = !!email && payload.email_verified === true && allowed.includes(email);
87
+ return { userId: payload.sub, email: payload.email, isAdmin };
88
+ }
89
+ };
90
+ }
91
+ export {
92
+ googleAuth,
93
+ verifyGoogleIdToken
94
+ };
95
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/auth/google/index.ts"],"sourcesContent":["import { createPublicKey, createVerify, type JsonWebKey } from \"node:crypto\";\nimport type { AuthAdapter, AuthIdentity } from \"../../types\";\n\n/**\n * Server auth adapter backed by **Google Identity Services** ID tokens — the\n * lightweight alternative to the Firebase adapter when all you want is \"Sign in\n * with Google\". No Firebase project, no service account: the browser obtains a\n * signed Google ID token (a JWT), drops it in a cookie, and this adapter\n * verifies it locally against Google's public keys, then gates on an email\n * allowlist.\n *\n * Verification is dependency-free (Node's built-in `crypto` + `fetch`), so the\n * package keeps its zero-runtime-deps contract.\n */\n\nconst GOOGLE_CERTS_URL = \"https://www.googleapis.com/oauth2/v3/certs\";\nconst GOOGLE_ISSUERS = [\"https://accounts.google.com\", \"accounts.google.com\"];\n\n/** A Google JWKS entry (RSA public key in JWK form). */\ninterface GoogleJwk {\n kid: string;\n kty: string;\n n: string;\n e: string;\n alg?: string;\n use?: string;\n}\n\nlet keyCache: { keys: Map<string, GoogleJwk>; expiresAt: number } | null = null;\n\n/** Fetch (and cache, honoring `cache-control: max-age`) Google's signing keys. */\nasync function getSigningKey(kid: string): Promise<GoogleJwk | null> {\n const now = Date.now();\n if (!keyCache || now >= keyCache.expiresAt) {\n const res = await fetch(GOOGLE_CERTS_URL);\n if (!res.ok) throw new Error(`Google JWKS fetch failed: ${res.status}`);\n const body = (await res.json()) as { keys: GoogleJwk[] };\n const keys = new Map(body.keys.map((k) => [k.kid, k]));\n const maxAge = /max-age=(\\d+)/.exec(res.headers.get(\"cache-control\") ?? \"\");\n keyCache = {\n keys,\n expiresAt: now + (maxAge ? Number(maxAge[1]) * 1000 : 3_600_000),\n };\n }\n return keyCache.keys.get(kid) ?? null;\n}\n\nfunction b64urlToBuffer(input: string): Buffer {\n return Buffer.from(input.replace(/-/g, \"+\").replace(/_/g, \"/\"), \"base64\");\n}\n\nfunction b64urlToJson<T>(input: string): T {\n return JSON.parse(b64urlToBuffer(input).toString(\"utf8\")) as T;\n}\n\nfunction readCookie(req: Request, name: string): string | null {\n const header = req.headers.get(\"cookie\");\n if (!header) return null;\n for (const part of header.split(\";\")) {\n const [k, ...v] = part.trim().split(\"=\");\n if (k === name) return decodeURIComponent(v.join(\"=\"));\n }\n return null;\n}\n\n/** Claims of interest from a verified Google ID token. */\nexport interface GoogleIdTokenPayload {\n iss: string;\n aud: string;\n sub: string;\n exp: number;\n iat: number;\n email?: string;\n email_verified?: boolean;\n name?: string;\n picture?: string;\n}\n\nexport interface VerifyGoogleIdTokenOptions {\n /** Expected audience — your OAuth 2.0 Web client ID(s). */\n clientId: string | string[];\n /** Accepted issuers. Defaults to Google's two canonical issuers. */\n issuers?: string[];\n /** Clock injection for tests. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/**\n * Verify a Google ID token end-to-end: RS256 signature against Google's JWKS,\n * plus `exp` / `iss` / `aud` checks. Throws on any failure; resolves with the\n * decoded payload on success. Exported standalone for custom flows/tests.\n */\nexport async function verifyGoogleIdToken(\n token: string,\n opts: VerifyGoogleIdTokenOptions,\n): Promise<GoogleIdTokenPayload> {\n const parts = token.split(\".\");\n if (parts.length !== 3) throw new Error(\"Malformed ID token\");\n const [headerB64, payloadB64, signatureB64] = parts;\n\n const header = b64urlToJson<{ alg: string; kid: string }>(headerB64);\n if (header.alg !== \"RS256\") throw new Error(`Unexpected alg: ${header.alg}`);\n\n const jwk = await getSigningKey(header.kid);\n if (!jwk) throw new Error(\"No matching Google signing key for token\");\n\n const publicKey = createPublicKey({\n key: jwk as unknown as JsonWebKey,\n format: \"jwk\",\n });\n const verifier = createVerify(\"RSA-SHA256\");\n verifier.update(`${headerB64}.${payloadB64}`);\n verifier.end();\n if (!verifier.verify(publicKey, b64urlToBuffer(signatureB64))) {\n throw new Error(\"Invalid ID token signature\");\n }\n\n const payload = b64urlToJson<GoogleIdTokenPayload>(payloadB64);\n const nowSec = Math.floor((opts.now?.() ?? Date.now()) / 1000);\n if (payload.exp <= nowSec) throw new Error(\"ID token expired\");\n\n const issuers = opts.issuers ?? GOOGLE_ISSUERS;\n if (!issuers.includes(payload.iss)) {\n throw new Error(`Untrusted issuer: ${payload.iss}`);\n }\n const audiences = Array.isArray(opts.clientId)\n ? opts.clientId\n : [opts.clientId];\n if (!audiences.includes(payload.aud)) throw new Error(\"Audience mismatch\");\n\n return payload;\n}\n\nexport interface GoogleAuthConfig {\n /** OAuth 2.0 Web client ID(s); the token's `aud` must match one of these. */\n clientId: string | string[];\n /** Allowlist of admin emails (compared case-insensitively). */\n adminEmails: string[];\n /** Cookie carrying the Google ID token. Default `adminToken`. */\n cookieName?: string;\n /** Accepted issuers (override for testing). */\n issuers?: string[];\n}\n\n/**\n * Build an {@link AuthAdapter} that verifies the Google ID token from the\n * request cookie and grants admin only to a verified email in `adminEmails`.\n */\nexport function googleAuth(config: GoogleAuthConfig): AuthAdapter {\n const cookieName = config.cookieName ?? \"adminToken\";\n const allowed = config.adminEmails.map((e) => e.trim().toLowerCase());\n\n return {\n async verifyRequest(req: Request): Promise<AuthIdentity | null> {\n const token = readCookie(req, cookieName);\n if (!token) return null;\n\n let payload: GoogleIdTokenPayload;\n try {\n payload = await verifyGoogleIdToken(token, {\n clientId: config.clientId,\n issuers: config.issuers,\n });\n } catch {\n // Expired/invalid/tampered → unauthorized, so the gate emits a\n // 401 { logout: true } and the client can force sign-out.\n return null;\n }\n\n const email = payload.email?.toLowerCase();\n const isAdmin =\n !!email && payload.email_verified === true && allowed.includes(email);\n\n return { userId: payload.sub, email: payload.email, isAdmin };\n },\n };\n}\n"],"mappings":"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","names":["_a"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalgoridim/headless-cms",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Database-agnostic, inline-edit headless CMS engine for React / Next.js apps",
5
5
  "license": "UNLICENSED",
6
6
  "author": "dalgoridim",
@@ -92,6 +92,16 @@
92
92
  "types": "./dist/auth/nextauth/index.d.ts",
93
93
  "import": "./dist/auth/nextauth/index.js",
94
94
  "require": "./dist/auth/nextauth/index.cjs"
95
+ },
96
+ "./auth/google": {
97
+ "types": "./dist/auth/google/index.d.ts",
98
+ "import": "./dist/auth/google/index.js",
99
+ "require": "./dist/auth/google/index.cjs"
100
+ },
101
+ "./auth/google/client": {
102
+ "types": "./dist/auth/google/client/index.d.ts",
103
+ "import": "./dist/auth/google/client/index.js",
104
+ "require": "./dist/auth/google/client/index.cjs"
95
105
  }
96
106
  },
97
107
  "files": [