@marcwelti/mw-core 0.2.0

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.
Files changed (40) hide show
  1. package/README.md +141 -0
  2. package/dist/context/index.d.mts +95 -0
  3. package/dist/context/index.d.ts +95 -0
  4. package/dist/context/index.js +280 -0
  5. package/dist/context/index.js.map +1 -0
  6. package/dist/context/index.mjs +276 -0
  7. package/dist/context/index.mjs.map +1 -0
  8. package/dist/firebase/index.d.mts +176 -0
  9. package/dist/firebase/index.d.ts +176 -0
  10. package/dist/firebase/index.js +393 -0
  11. package/dist/firebase/index.js.map +1 -0
  12. package/dist/firebase/index.mjs +318 -0
  13. package/dist/firebase/index.mjs.map +1 -0
  14. package/dist/hooks/index.d.mts +97 -0
  15. package/dist/hooks/index.d.ts +97 -0
  16. package/dist/hooks/index.js +618 -0
  17. package/dist/hooks/index.js.map +1 -0
  18. package/dist/hooks/index.mjs +611 -0
  19. package/dist/hooks/index.mjs.map +1 -0
  20. package/dist/index.d.mts +12 -0
  21. package/dist/index.d.ts +12 -0
  22. package/dist/index.js +922 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/index.mjs +843 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/server/index.d.mts +216 -0
  27. package/dist/server/index.d.ts +216 -0
  28. package/dist/server/index.js +309 -0
  29. package/dist/server/index.js.map +1 -0
  30. package/dist/server/index.mjs +276 -0
  31. package/dist/server/index.mjs.map +1 -0
  32. package/dist/storage-BU_rfYCi.d.mts +43 -0
  33. package/dist/storage-BU_rfYCi.d.ts +43 -0
  34. package/dist/types/index.d.mts +108 -0
  35. package/dist/types/index.d.ts +108 -0
  36. package/dist/types/index.js +12 -0
  37. package/dist/types/index.js.map +1 -0
  38. package/dist/types/index.mjs +3 -0
  39. package/dist/types/index.mjs.map +1 -0
  40. package/package.json +91 -0
@@ -0,0 +1,309 @@
1
+ 'use strict';
2
+
3
+ var app = require('firebase-admin/app');
4
+ var auth = require('firebase-admin/auth');
5
+ var firestore = require('firebase-admin/firestore');
6
+
7
+ // src/server/admin.ts
8
+ var _adminApp = null;
9
+ var _adminAuth = null;
10
+ var _adminDb = null;
11
+ function getAdminCredentials() {
12
+ const projectId = process.env.FIREBASE_ADMIN_PROJECT_ID;
13
+ const clientEmail = process.env.FIREBASE_ADMIN_CLIENT_EMAIL;
14
+ const privateKey = process.env.FIREBASE_ADMIN_PRIVATE_KEY?.replace(/\\n/g, "\n");
15
+ if (!projectId || !clientEmail || !privateKey) {
16
+ throw new Error(
17
+ "[mw-core] Missing Firebase Admin credentials. Set FIREBASE_ADMIN_PROJECT_ID, FIREBASE_ADMIN_CLIENT_EMAIL, and FIREBASE_ADMIN_PRIVATE_KEY environment variables."
18
+ );
19
+ }
20
+ return {
21
+ projectId,
22
+ clientEmail,
23
+ privateKey
24
+ };
25
+ }
26
+ function initializeAdminApp() {
27
+ if (_adminApp) {
28
+ return _adminApp;
29
+ }
30
+ const existingApps = app.getApps();
31
+ if (existingApps.length > 0) {
32
+ _adminApp = existingApps[0];
33
+ return _adminApp;
34
+ }
35
+ const credentials = getAdminCredentials();
36
+ _adminApp = app.initializeApp({
37
+ credential: app.cert(credentials)
38
+ });
39
+ return _adminApp;
40
+ }
41
+ function getAdminAuth() {
42
+ if (!_adminAuth) {
43
+ _adminAuth = auth.getAuth(initializeAdminApp());
44
+ }
45
+ return _adminAuth;
46
+ }
47
+ function getAdminFirestore() {
48
+ if (!_adminDb) {
49
+ _adminDb = firestore.getFirestore(initializeAdminApp());
50
+ }
51
+ return _adminDb;
52
+ }
53
+
54
+ // src/server/session.ts
55
+ var DEFAULT_SESSION_DURATION = 60 * 60 * 24 * 5 * 1e3;
56
+ async function createSessionCookie(idToken, config) {
57
+ const auth = getAdminAuth();
58
+ const expiresIn = config.expiresIn || DEFAULT_SESSION_DURATION;
59
+ const sessionCookie = await auth.createSessionCookie(idToken, { expiresIn });
60
+ return sessionCookie;
61
+ }
62
+ function generateLoginCookieHeader(sessionCookie, config) {
63
+ const expiresIn = config.expiresIn || DEFAULT_SESSION_DURATION;
64
+ const maxAge = Math.floor(expiresIn / 1e3);
65
+ const secure = config.secure ?? process.env.NODE_ENV === "production";
66
+ const path = config.path || "/";
67
+ const parts = [
68
+ `session=${sessionCookie}`,
69
+ `Domain=${config.domain}`,
70
+ `Path=${path}`,
71
+ "HttpOnly",
72
+ "SameSite=Lax",
73
+ `Max-Age=${maxAge}`
74
+ ];
75
+ if (secure) {
76
+ parts.push("Secure");
77
+ }
78
+ return parts.join("; ");
79
+ }
80
+ function generateLogoutCookieHeader(config) {
81
+ const path = config.path || "/";
82
+ const secure = config.secure ?? process.env.NODE_ENV === "production";
83
+ const parts = [
84
+ "session=",
85
+ `Domain=${config.domain}`,
86
+ `Path=${path}`,
87
+ "HttpOnly",
88
+ "SameSite=Lax",
89
+ "Max-Age=0"
90
+ ];
91
+ if (secure) {
92
+ parts.push("Secure");
93
+ }
94
+ return parts.join("; ");
95
+ }
96
+ async function verifySessionCookie(sessionCookie, checkRevoked = true) {
97
+ const auth = getAdminAuth();
98
+ return auth.verifySessionCookie(sessionCookie, checkRevoked);
99
+ }
100
+ async function verifyIdToken(idToken) {
101
+ const auth = getAdminAuth();
102
+ return auth.verifyIdToken(idToken);
103
+ }
104
+ async function revokeUserTokens(uid) {
105
+ const auth = getAdminAuth();
106
+ await auth.revokeRefreshTokens(uid);
107
+ }
108
+ async function getSessionUser(sessionCookie) {
109
+ if (!sessionCookie) {
110
+ return null;
111
+ }
112
+ try {
113
+ const decodedClaims = await verifySessionCookie(sessionCookie);
114
+ return {
115
+ uid: decodedClaims.uid,
116
+ email: decodedClaims.email,
117
+ emailVerified: decodedClaims.email_verified || false,
118
+ displayName: decodedClaims.name,
119
+ photoURL: decodedClaims.picture
120
+ };
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+
126
+ // src/server/apiRoutes.ts
127
+ async function handleLogin(idToken, config) {
128
+ await verifyIdToken(idToken);
129
+ const sessionCookie = await createSessionCookie(idToken, config);
130
+ return generateLoginCookieHeader(sessionCookie, config);
131
+ }
132
+ function handleLogout(config) {
133
+ return generateLogoutCookieHeader(config);
134
+ }
135
+ async function handleSession(sessionCookie) {
136
+ if (!sessionCookie) {
137
+ return { authenticated: false };
138
+ }
139
+ try {
140
+ const decodedClaims = await verifySessionCookie(sessionCookie);
141
+ return {
142
+ authenticated: true,
143
+ user: {
144
+ uid: decodedClaims.uid,
145
+ email: decodedClaims.email,
146
+ emailVerified: decodedClaims.email_verified || false,
147
+ displayName: decodedClaims.name,
148
+ photoURL: decodedClaims.picture
149
+ }
150
+ };
151
+ } catch {
152
+ return { authenticated: false };
153
+ }
154
+ }
155
+ var DEFAULT_SESSION_CONFIG = {
156
+ domain: ".marc-welti.ch",
157
+ expiresIn: 60 * 60 * 24 * 5 * 1e3,
158
+ // 5 days
159
+ path: "/",
160
+ secure: process.env.NODE_ENV === "production"
161
+ };
162
+ function createSessionConfig(domain, options) {
163
+ return {
164
+ ...DEFAULT_SESSION_CONFIG,
165
+ ...options,
166
+ domain
167
+ };
168
+ }
169
+
170
+ // src/server/middleware.ts
171
+ async function verifyAuth(sessionCookie) {
172
+ if (!sessionCookie) {
173
+ return { authenticated: false, user: null };
174
+ }
175
+ try {
176
+ const decodedClaims = await verifySessionCookie(sessionCookie);
177
+ return {
178
+ authenticated: true,
179
+ user: {
180
+ uid: decodedClaims.uid,
181
+ email: decodedClaims.email,
182
+ emailVerified: decodedClaims.email_verified || false,
183
+ displayName: decodedClaims.name,
184
+ photoURL: decodedClaims.picture
185
+ }
186
+ };
187
+ } catch {
188
+ return { authenticated: false, user: null };
189
+ }
190
+ }
191
+ function isProtectedPath(pathname, protectedPaths) {
192
+ return protectedPaths.some((path) => {
193
+ return pathname === path || pathname.startsWith(`${path}/`);
194
+ });
195
+ }
196
+ var DEFAULT_MIDDLEWARE_CONFIG = {
197
+ protectedPaths: ["/account", "/dashboard"],
198
+ loginRedirectPath: "/login-required",
199
+ cookieName: "session"
200
+ };
201
+ function createMiddlewareConfig(protectedPaths, options) {
202
+ return {
203
+ ...DEFAULT_MIDDLEWARE_CONFIG,
204
+ ...options,
205
+ protectedPaths
206
+ };
207
+ }
208
+
209
+ // src/server/roles.ts
210
+ var ROLE_DESCRIPTIONS = {
211
+ owner: "Full system access (Marc only)",
212
+ employee: "Limited access to assigned customers",
213
+ admin: "Technical support access",
214
+ user: "Customer access"
215
+ };
216
+ function hasRole(userRole, requiredRoles) {
217
+ if (!userRole) return false;
218
+ return requiredRoles.includes(userRole);
219
+ }
220
+ function isStaff(userRole) {
221
+ return hasRole(userRole, ["owner", "admin", "employee"]);
222
+ }
223
+ function isAdmin(userRole) {
224
+ return hasRole(userRole, ["owner", "admin"]);
225
+ }
226
+ function isOwner(userRole) {
227
+ return userRole === "owner";
228
+ }
229
+ function getRoleDisplayName(role) {
230
+ const names = {
231
+ owner: "Owner",
232
+ employee: "Employee",
233
+ admin: "Administrator",
234
+ user: "User"
235
+ };
236
+ return names[role] || role;
237
+ }
238
+
239
+ // src/server/tiers.ts
240
+ var ALL_TIERS = [
241
+ "MasterClass",
242
+ "MasterClassLight",
243
+ "PremiumOnlineKurs",
244
+ "MasterOnlineKurs",
245
+ "OnlineKursPro",
246
+ "PremiumTraining",
247
+ "VIPTraining"
248
+ ];
249
+ var TIER_DISPLAY_NAMES = {
250
+ MasterClass: "MasterClass",
251
+ MasterClassLight: "MasterClass Light",
252
+ PremiumOnlineKurs: "Premium Online Kurs",
253
+ MasterOnlineKurs: "Master Online Kurs",
254
+ OnlineKursPro: "Online Kurs Pro",
255
+ PremiumTraining: "Premium Training",
256
+ VIPTraining: "VIP Training"
257
+ };
258
+ function hasAccess(userTier, requiredTiers) {
259
+ if (!userTier) return false;
260
+ return requiredTiers.includes(userTier);
261
+ }
262
+ function hasSubscription(userTier) {
263
+ return userTier !== null && userTier !== void 0;
264
+ }
265
+ function getTierDisplayName(tier) {
266
+ return TIER_DISPLAY_NAMES[tier] || tier;
267
+ }
268
+ function isSubscriptionExpired(subscription) {
269
+ if (!subscription.expirationDate) return false;
270
+ return /* @__PURE__ */ new Date() > subscription.expirationDate;
271
+ }
272
+ function isSubscriptionActive(subscription) {
273
+ return subscription.isActive && !isSubscriptionExpired(subscription);
274
+ }
275
+
276
+ exports.ALL_TIERS = ALL_TIERS;
277
+ exports.DEFAULT_MIDDLEWARE_CONFIG = DEFAULT_MIDDLEWARE_CONFIG;
278
+ exports.DEFAULT_SESSION_CONFIG = DEFAULT_SESSION_CONFIG;
279
+ exports.ROLE_DESCRIPTIONS = ROLE_DESCRIPTIONS;
280
+ exports.TIER_DISPLAY_NAMES = TIER_DISPLAY_NAMES;
281
+ exports.createMiddlewareConfig = createMiddlewareConfig;
282
+ exports.createSessionConfig = createSessionConfig;
283
+ exports.createSessionCookie = createSessionCookie;
284
+ exports.generateLoginCookieHeader = generateLoginCookieHeader;
285
+ exports.generateLogoutCookieHeader = generateLogoutCookieHeader;
286
+ exports.getAdminAuth = getAdminAuth;
287
+ exports.getAdminFirestore = getAdminFirestore;
288
+ exports.getRoleDisplayName = getRoleDisplayName;
289
+ exports.getSessionUser = getSessionUser;
290
+ exports.getTierDisplayName = getTierDisplayName;
291
+ exports.handleLogin = handleLogin;
292
+ exports.handleLogout = handleLogout;
293
+ exports.handleSession = handleSession;
294
+ exports.hasAccess = hasAccess;
295
+ exports.hasRole = hasRole;
296
+ exports.hasSubscription = hasSubscription;
297
+ exports.initializeAdminApp = initializeAdminApp;
298
+ exports.isAdmin = isAdmin;
299
+ exports.isOwner = isOwner;
300
+ exports.isProtectedPath = isProtectedPath;
301
+ exports.isStaff = isStaff;
302
+ exports.isSubscriptionActive = isSubscriptionActive;
303
+ exports.isSubscriptionExpired = isSubscriptionExpired;
304
+ exports.revokeUserTokens = revokeUserTokens;
305
+ exports.verifyAuth = verifyAuth;
306
+ exports.verifyIdToken = verifyIdToken;
307
+ exports.verifySessionCookie = verifySessionCookie;
308
+ //# sourceMappingURL=index.js.map
309
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/admin.ts","../../src/server/session.ts","../../src/server/apiRoutes.ts","../../src/server/middleware.ts","../../src/server/roles.ts","../../src/server/tiers.ts"],"names":["getApps","initializeApp","cert","getAuth","getFirestore"],"mappings":"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","file":"index.js","sourcesContent":["import {\n initializeApp,\n getApps,\n cert,\n App,\n ServiceAccount,\n} from 'firebase-admin/app';\nimport { getAuth, Auth } from 'firebase-admin/auth';\nimport { getFirestore, Firestore } from 'firebase-admin/firestore';\n\nlet _adminApp: App | null = null;\nlet _adminAuth: Auth | null = null;\nlet _adminDb: Firestore | null = null;\n\n/**\n * Get Firebase Admin credentials from environment variables\n */\nfunction getAdminCredentials(): ServiceAccount {\n const projectId = process.env.FIREBASE_ADMIN_PROJECT_ID;\n const clientEmail = process.env.FIREBASE_ADMIN_CLIENT_EMAIL;\n const privateKey = process.env.FIREBASE_ADMIN_PRIVATE_KEY?.replace(/\\\\n/g, '\\n');\n\n if (!projectId || !clientEmail || !privateKey) {\n throw new Error(\n '[mw-core] Missing Firebase Admin credentials. ' +\n 'Set FIREBASE_ADMIN_PROJECT_ID, FIREBASE_ADMIN_CLIENT_EMAIL, and FIREBASE_ADMIN_PRIVATE_KEY environment variables.'\n );\n }\n\n return {\n projectId,\n clientEmail,\n privateKey,\n };\n}\n\n/**\n * Initialize Firebase Admin SDK (singleton)\n */\nexport function initializeAdminApp(): App {\n if (_adminApp) {\n return _adminApp;\n }\n\n const existingApps = getApps();\n if (existingApps.length > 0) {\n _adminApp = existingApps[0];\n return _adminApp;\n }\n\n const credentials = getAdminCredentials();\n _adminApp = initializeApp({\n credential: cert(credentials),\n });\n\n return _adminApp;\n}\n\n/**\n * Get Firebase Admin Auth instance\n */\nexport function getAdminAuth(): Auth {\n if (!_adminAuth) {\n _adminAuth = getAuth(initializeAdminApp());\n }\n return _adminAuth;\n}\n\n/**\n * Get Firebase Admin Firestore instance\n */\nexport function getAdminFirestore(): Firestore {\n if (!_adminDb) {\n _adminDb = getFirestore(initializeAdminApp());\n }\n return _adminDb;\n}\n\n// Re-export types\nexport type { App, Auth, Firestore };\n\n","import { getAdminAuth } from './admin';\nimport { DecodedIdToken } from 'firebase-admin/auth';\n\n/**\n * Session cookie configuration\n */\nexport interface SessionConfig {\n /** Cookie domain (e.g., '.marc-welti.ch') */\n domain: string;\n /** Session duration in milliseconds (default: 5 days) */\n expiresIn?: number;\n /** Cookie path (default: '/') */\n path?: string;\n /** Use secure cookies (default: true in production) */\n secure?: boolean;\n}\n\nconst DEFAULT_SESSION_DURATION = 60 * 60 * 24 * 5 * 1000; // 5 days in milliseconds\n\n/**\n * Create a session cookie from a Firebase ID token\n */\nexport async function createSessionCookie(\n idToken: string,\n config: SessionConfig\n): Promise<string> {\n const auth = getAdminAuth();\n const expiresIn = config.expiresIn || DEFAULT_SESSION_DURATION;\n\n // Create session cookie\n const sessionCookie = await auth.createSessionCookie(idToken, { expiresIn });\n\n return sessionCookie;\n}\n\n/**\n * Generate the Set-Cookie header string for login\n */\nexport function generateLoginCookieHeader(\n sessionCookie: string,\n config: SessionConfig\n): string {\n const expiresIn = config.expiresIn || DEFAULT_SESSION_DURATION;\n const maxAge = Math.floor(expiresIn / 1000); // Convert to seconds\n const secure = config.secure ?? process.env.NODE_ENV === 'production';\n const path = config.path || '/';\n\n const parts = [\n `session=${sessionCookie}`,\n `Domain=${config.domain}`,\n `Path=${path}`,\n 'HttpOnly',\n 'SameSite=Lax',\n `Max-Age=${maxAge}`,\n ];\n\n if (secure) {\n parts.push('Secure');\n }\n\n return parts.join('; ');\n}\n\n/**\n * Generate the Set-Cookie header string for logout\n */\nexport function generateLogoutCookieHeader(config: SessionConfig): string {\n const path = config.path || '/';\n const secure = config.secure ?? process.env.NODE_ENV === 'production';\n\n const parts = [\n 'session=',\n `Domain=${config.domain}`,\n `Path=${path}`,\n 'HttpOnly',\n 'SameSite=Lax',\n 'Max-Age=0',\n ];\n\n if (secure) {\n parts.push('Secure');\n }\n\n return parts.join('; ');\n}\n\n/**\n * Verify a session cookie and return decoded claims\n */\nexport async function verifySessionCookie(\n sessionCookie: string,\n checkRevoked: boolean = true\n): Promise<DecodedIdToken> {\n const auth = getAdminAuth();\n return auth.verifySessionCookie(sessionCookie, checkRevoked);\n}\n\n/**\n * Verify an ID token (for initial login)\n */\nexport async function verifyIdToken(idToken: string): Promise<DecodedIdToken> {\n const auth = getAdminAuth();\n return auth.verifyIdToken(idToken);\n}\n\n/**\n * Revoke all refresh tokens for a user (force logout everywhere)\n */\nexport async function revokeUserTokens(uid: string): Promise<void> {\n const auth = getAdminAuth();\n await auth.revokeRefreshTokens(uid);\n}\n\n/**\n * Session user data extracted from verified session\n */\nexport interface SessionUser {\n uid: string;\n email: string | undefined;\n emailVerified: boolean;\n displayName?: string;\n photoURL?: string;\n}\n\n/**\n * Verify session and return user data\n */\nexport async function getSessionUser(\n sessionCookie: string | undefined\n): Promise<SessionUser | null> {\n if (!sessionCookie) {\n return null;\n }\n\n try {\n const decodedClaims = await verifySessionCookie(sessionCookie);\n return {\n uid: decodedClaims.uid,\n email: decodedClaims.email,\n emailVerified: decodedClaims.email_verified || false,\n displayName: decodedClaims.name,\n photoURL: decodedClaims.picture,\n };\n } catch {\n return null;\n }\n}\n\n// Re-export types\nexport type { DecodedIdToken };\n\n","import {\n createSessionCookie,\n generateLoginCookieHeader,\n generateLogoutCookieHeader,\n verifySessionCookie,\n verifyIdToken,\n SessionConfig,\n SessionUser,\n} from './session';\n\n/**\n * Handle login: verify ID token and create session cookie\n * Returns the Set-Cookie header value\n */\nexport async function handleLogin(\n idToken: string,\n config: SessionConfig\n): Promise<string> {\n // Verify the ID token first\n await verifyIdToken(idToken);\n \n // Create session cookie\n const sessionCookie = await createSessionCookie(idToken, config);\n \n // Generate Set-Cookie header\n return generateLoginCookieHeader(sessionCookie, config);\n}\n\n/**\n * Handle logout: generate cookie clearing header\n * Returns the Set-Cookie header value\n */\nexport function handleLogout(config: SessionConfig): string {\n return generateLogoutCookieHeader(config);\n}\n\n/**\n * Handle session verification\n * Returns session user data or null if invalid\n */\nexport async function handleSession(\n sessionCookie: string | undefined\n): Promise<{ authenticated: boolean; user?: SessionUser }> {\n if (!sessionCookie) {\n return { authenticated: false };\n }\n\n try {\n const decodedClaims = await verifySessionCookie(sessionCookie);\n return {\n authenticated: true,\n user: {\n uid: decodedClaims.uid,\n email: decodedClaims.email,\n emailVerified: decodedClaims.email_verified || false,\n displayName: decodedClaims.name,\n photoURL: decodedClaims.picture,\n },\n };\n } catch {\n return { authenticated: false };\n }\n}\n\n/**\n * Default session config for marc-welti.ch\n */\nexport const DEFAULT_SESSION_CONFIG: SessionConfig = {\n domain: '.marc-welti.ch',\n expiresIn: 60 * 60 * 24 * 5 * 1000, // 5 days\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n};\n\n/**\n * Create session config with custom domain\n */\nexport function createSessionConfig(\n domain: string,\n options?: Partial<Omit<SessionConfig, 'domain'>>\n): SessionConfig {\n return {\n ...DEFAULT_SESSION_CONFIG,\n ...options,\n domain,\n };\n}\n\n","import { verifySessionCookie, SessionUser } from './session';\n\n/**\n * Result of middleware authentication check\n */\nexport interface AuthResult {\n authenticated: boolean;\n user: SessionUser | null;\n}\n\n/**\n * Verify session from cookie value\n * Use this in Next.js middleware or API routes\n */\nexport async function verifyAuth(\n sessionCookie: string | undefined\n): Promise<AuthResult> {\n if (!sessionCookie) {\n return { authenticated: false, user: null };\n }\n\n try {\n const decodedClaims = await verifySessionCookie(sessionCookie);\n return {\n authenticated: true,\n user: {\n uid: decodedClaims.uid,\n email: decodedClaims.email,\n emailVerified: decodedClaims.email_verified || false,\n displayName: decodedClaims.name,\n photoURL: decodedClaims.picture,\n },\n };\n } catch {\n return { authenticated: false, user: null };\n }\n}\n\n/**\n * Check if a path matches any of the protected patterns\n */\nexport function isProtectedPath(\n pathname: string,\n protectedPaths: string[]\n): boolean {\n return protectedPaths.some((path) => {\n // Exact match or starts with path\n return pathname === path || pathname.startsWith(`${path}/`);\n });\n}\n\n/**\n * Configuration for auth middleware\n */\nexport interface MiddlewareConfig {\n /** Paths that require authentication */\n protectedPaths: string[];\n /** Path to redirect unauthenticated users to */\n loginRedirectPath?: string;\n /** Cookie name (default: 'session') */\n cookieName?: string;\n}\n\n/**\n * Default middleware configuration\n */\nexport const DEFAULT_MIDDLEWARE_CONFIG: MiddlewareConfig = {\n protectedPaths: ['/account', '/dashboard'],\n loginRedirectPath: '/login-required',\n cookieName: 'session',\n};\n\n/**\n * Create middleware config with custom options\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[],\n options?: Partial<Omit<MiddlewareConfig, 'protectedPaths'>>\n): MiddlewareConfig {\n return {\n ...DEFAULT_MIDDLEWARE_CONFIG,\n ...options,\n protectedPaths,\n };\n}\n\n","/**\n * User roles in the system\n */\nexport type UserRole = 'owner' | 'employee' | 'admin' | 'user';\n\n/**\n * Role hierarchy and permissions\n * \n * - owner: Marc only, full system access\n * - employee: Team members, limited to assigned customers\n * - admin: External developers/support, full technical access\n * - user: Customers, customer-facing features only\n */\nexport const ROLE_DESCRIPTIONS: Record<UserRole, string> = {\n owner: 'Full system access (Marc only)',\n employee: 'Limited access to assigned customers',\n admin: 'Technical support access',\n user: 'Customer access',\n};\n\n/**\n * Check if user has one of the required roles\n */\nexport function hasRole(\n userRole: UserRole | undefined | null,\n requiredRoles: UserRole[]\n): boolean {\n if (!userRole) return false;\n return requiredRoles.includes(userRole);\n}\n\n/**\n * Check if user is an admin-level role (owner, admin, or employee)\n */\nexport function isStaff(userRole: UserRole | undefined | null): boolean {\n return hasRole(userRole, ['owner', 'admin', 'employee']);\n}\n\n/**\n * Check if user has full admin access (owner or admin)\n */\nexport function isAdmin(userRole: UserRole | undefined | null): boolean {\n return hasRole(userRole, ['owner', 'admin']);\n}\n\n/**\n * Check if user is the owner\n */\nexport function isOwner(userRole: UserRole | undefined | null): boolean {\n return userRole === 'owner';\n}\n\n/**\n * Get role display name\n */\nexport function getRoleDisplayName(role: UserRole): string {\n const names: Record<UserRole, string> = {\n owner: 'Owner',\n employee: 'Employee',\n admin: 'Administrator',\n user: 'User',\n };\n return names[role] || role;\n}\n\n","/**\n * Subscription tiers (independent, not hierarchical)\n */\nexport type SubscriptionTier =\n | 'MasterClass'\n | 'MasterClassLight'\n | 'PremiumOnlineKurs'\n | 'MasterOnlineKurs'\n | 'OnlineKursPro'\n | 'PremiumTraining'\n | 'VIPTraining';\n\n/**\n * All available tiers\n */\nexport const ALL_TIERS: SubscriptionTier[] = [\n 'MasterClass',\n 'MasterClassLight',\n 'PremiumOnlineKurs',\n 'MasterOnlineKurs',\n 'OnlineKursPro',\n 'PremiumTraining',\n 'VIPTraining',\n];\n\n/**\n * Tier display names (internal only - not shown to customers)\n */\nexport const TIER_DISPLAY_NAMES: Record<SubscriptionTier, string> = {\n MasterClass: 'MasterClass',\n MasterClassLight: 'MasterClass Light',\n PremiumOnlineKurs: 'Premium Online Kurs',\n MasterOnlineKurs: 'Master Online Kurs',\n OnlineKursPro: 'Online Kurs Pro',\n PremiumTraining: 'Premium Training',\n VIPTraining: 'VIP Training',\n};\n\n/**\n * Check if user has access to a tier-gated feature\n * \n * @param userTier - The user's current subscription tier (or null if none)\n * @param requiredTiers - List of tiers that have access to the feature\n * @returns true if user's tier is in the required list\n */\nexport function hasAccess(\n userTier: SubscriptionTier | null | undefined,\n requiredTiers: SubscriptionTier[]\n): boolean {\n if (!userTier) return false;\n return requiredTiers.includes(userTier);\n}\n\n/**\n * Check if user has any active subscription\n */\nexport function hasSubscription(\n userTier: SubscriptionTier | null | undefined\n): boolean {\n return userTier !== null && userTier !== undefined;\n}\n\n/**\n * Get tier display name\n */\nexport function getTierDisplayName(tier: SubscriptionTier): string {\n return TIER_DISPLAY_NAMES[tier] || tier;\n}\n\n/**\n * Subscription data stored in Firestore\n */\nexport interface UserSubscription {\n tier: SubscriptionTier;\n startDate: Date;\n expirationDate?: Date;\n isActive: boolean;\n}\n\n/**\n * Check if subscription is expired\n */\nexport function isSubscriptionExpired(subscription: UserSubscription): boolean {\n if (!subscription.expirationDate) return false;\n return new Date() > subscription.expirationDate;\n}\n\n/**\n * Check if subscription is currently active\n */\nexport function isSubscriptionActive(subscription: UserSubscription): boolean {\n return subscription.isActive && !isSubscriptionExpired(subscription);\n}\n\n"]}
@@ -0,0 +1,276 @@
1
+ import { getApps, initializeApp, cert } from 'firebase-admin/app';
2
+ import { getAuth } from 'firebase-admin/auth';
3
+ import { getFirestore } from 'firebase-admin/firestore';
4
+
5
+ // src/server/admin.ts
6
+ var _adminApp = null;
7
+ var _adminAuth = null;
8
+ var _adminDb = null;
9
+ function getAdminCredentials() {
10
+ const projectId = process.env.FIREBASE_ADMIN_PROJECT_ID;
11
+ const clientEmail = process.env.FIREBASE_ADMIN_CLIENT_EMAIL;
12
+ const privateKey = process.env.FIREBASE_ADMIN_PRIVATE_KEY?.replace(/\\n/g, "\n");
13
+ if (!projectId || !clientEmail || !privateKey) {
14
+ throw new Error(
15
+ "[mw-core] Missing Firebase Admin credentials. Set FIREBASE_ADMIN_PROJECT_ID, FIREBASE_ADMIN_CLIENT_EMAIL, and FIREBASE_ADMIN_PRIVATE_KEY environment variables."
16
+ );
17
+ }
18
+ return {
19
+ projectId,
20
+ clientEmail,
21
+ privateKey
22
+ };
23
+ }
24
+ function initializeAdminApp() {
25
+ if (_adminApp) {
26
+ return _adminApp;
27
+ }
28
+ const existingApps = getApps();
29
+ if (existingApps.length > 0) {
30
+ _adminApp = existingApps[0];
31
+ return _adminApp;
32
+ }
33
+ const credentials = getAdminCredentials();
34
+ _adminApp = initializeApp({
35
+ credential: cert(credentials)
36
+ });
37
+ return _adminApp;
38
+ }
39
+ function getAdminAuth() {
40
+ if (!_adminAuth) {
41
+ _adminAuth = getAuth(initializeAdminApp());
42
+ }
43
+ return _adminAuth;
44
+ }
45
+ function getAdminFirestore() {
46
+ if (!_adminDb) {
47
+ _adminDb = getFirestore(initializeAdminApp());
48
+ }
49
+ return _adminDb;
50
+ }
51
+
52
+ // src/server/session.ts
53
+ var DEFAULT_SESSION_DURATION = 60 * 60 * 24 * 5 * 1e3;
54
+ async function createSessionCookie(idToken, config) {
55
+ const auth = getAdminAuth();
56
+ const expiresIn = config.expiresIn || DEFAULT_SESSION_DURATION;
57
+ const sessionCookie = await auth.createSessionCookie(idToken, { expiresIn });
58
+ return sessionCookie;
59
+ }
60
+ function generateLoginCookieHeader(sessionCookie, config) {
61
+ const expiresIn = config.expiresIn || DEFAULT_SESSION_DURATION;
62
+ const maxAge = Math.floor(expiresIn / 1e3);
63
+ const secure = config.secure ?? process.env.NODE_ENV === "production";
64
+ const path = config.path || "/";
65
+ const parts = [
66
+ `session=${sessionCookie}`,
67
+ `Domain=${config.domain}`,
68
+ `Path=${path}`,
69
+ "HttpOnly",
70
+ "SameSite=Lax",
71
+ `Max-Age=${maxAge}`
72
+ ];
73
+ if (secure) {
74
+ parts.push("Secure");
75
+ }
76
+ return parts.join("; ");
77
+ }
78
+ function generateLogoutCookieHeader(config) {
79
+ const path = config.path || "/";
80
+ const secure = config.secure ?? process.env.NODE_ENV === "production";
81
+ const parts = [
82
+ "session=",
83
+ `Domain=${config.domain}`,
84
+ `Path=${path}`,
85
+ "HttpOnly",
86
+ "SameSite=Lax",
87
+ "Max-Age=0"
88
+ ];
89
+ if (secure) {
90
+ parts.push("Secure");
91
+ }
92
+ return parts.join("; ");
93
+ }
94
+ async function verifySessionCookie(sessionCookie, checkRevoked = true) {
95
+ const auth = getAdminAuth();
96
+ return auth.verifySessionCookie(sessionCookie, checkRevoked);
97
+ }
98
+ async function verifyIdToken(idToken) {
99
+ const auth = getAdminAuth();
100
+ return auth.verifyIdToken(idToken);
101
+ }
102
+ async function revokeUserTokens(uid) {
103
+ const auth = getAdminAuth();
104
+ await auth.revokeRefreshTokens(uid);
105
+ }
106
+ async function getSessionUser(sessionCookie) {
107
+ if (!sessionCookie) {
108
+ return null;
109
+ }
110
+ try {
111
+ const decodedClaims = await verifySessionCookie(sessionCookie);
112
+ return {
113
+ uid: decodedClaims.uid,
114
+ email: decodedClaims.email,
115
+ emailVerified: decodedClaims.email_verified || false,
116
+ displayName: decodedClaims.name,
117
+ photoURL: decodedClaims.picture
118
+ };
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ // src/server/apiRoutes.ts
125
+ async function handleLogin(idToken, config) {
126
+ await verifyIdToken(idToken);
127
+ const sessionCookie = await createSessionCookie(idToken, config);
128
+ return generateLoginCookieHeader(sessionCookie, config);
129
+ }
130
+ function handleLogout(config) {
131
+ return generateLogoutCookieHeader(config);
132
+ }
133
+ async function handleSession(sessionCookie) {
134
+ if (!sessionCookie) {
135
+ return { authenticated: false };
136
+ }
137
+ try {
138
+ const decodedClaims = await verifySessionCookie(sessionCookie);
139
+ return {
140
+ authenticated: true,
141
+ user: {
142
+ uid: decodedClaims.uid,
143
+ email: decodedClaims.email,
144
+ emailVerified: decodedClaims.email_verified || false,
145
+ displayName: decodedClaims.name,
146
+ photoURL: decodedClaims.picture
147
+ }
148
+ };
149
+ } catch {
150
+ return { authenticated: false };
151
+ }
152
+ }
153
+ var DEFAULT_SESSION_CONFIG = {
154
+ domain: ".marc-welti.ch",
155
+ expiresIn: 60 * 60 * 24 * 5 * 1e3,
156
+ // 5 days
157
+ path: "/",
158
+ secure: process.env.NODE_ENV === "production"
159
+ };
160
+ function createSessionConfig(domain, options) {
161
+ return {
162
+ ...DEFAULT_SESSION_CONFIG,
163
+ ...options,
164
+ domain
165
+ };
166
+ }
167
+
168
+ // src/server/middleware.ts
169
+ async function verifyAuth(sessionCookie) {
170
+ if (!sessionCookie) {
171
+ return { authenticated: false, user: null };
172
+ }
173
+ try {
174
+ const decodedClaims = await verifySessionCookie(sessionCookie);
175
+ return {
176
+ authenticated: true,
177
+ user: {
178
+ uid: decodedClaims.uid,
179
+ email: decodedClaims.email,
180
+ emailVerified: decodedClaims.email_verified || false,
181
+ displayName: decodedClaims.name,
182
+ photoURL: decodedClaims.picture
183
+ }
184
+ };
185
+ } catch {
186
+ return { authenticated: false, user: null };
187
+ }
188
+ }
189
+ function isProtectedPath(pathname, protectedPaths) {
190
+ return protectedPaths.some((path) => {
191
+ return pathname === path || pathname.startsWith(`${path}/`);
192
+ });
193
+ }
194
+ var DEFAULT_MIDDLEWARE_CONFIG = {
195
+ protectedPaths: ["/account", "/dashboard"],
196
+ loginRedirectPath: "/login-required",
197
+ cookieName: "session"
198
+ };
199
+ function createMiddlewareConfig(protectedPaths, options) {
200
+ return {
201
+ ...DEFAULT_MIDDLEWARE_CONFIG,
202
+ ...options,
203
+ protectedPaths
204
+ };
205
+ }
206
+
207
+ // src/server/roles.ts
208
+ var ROLE_DESCRIPTIONS = {
209
+ owner: "Full system access (Marc only)",
210
+ employee: "Limited access to assigned customers",
211
+ admin: "Technical support access",
212
+ user: "Customer access"
213
+ };
214
+ function hasRole(userRole, requiredRoles) {
215
+ if (!userRole) return false;
216
+ return requiredRoles.includes(userRole);
217
+ }
218
+ function isStaff(userRole) {
219
+ return hasRole(userRole, ["owner", "admin", "employee"]);
220
+ }
221
+ function isAdmin(userRole) {
222
+ return hasRole(userRole, ["owner", "admin"]);
223
+ }
224
+ function isOwner(userRole) {
225
+ return userRole === "owner";
226
+ }
227
+ function getRoleDisplayName(role) {
228
+ const names = {
229
+ owner: "Owner",
230
+ employee: "Employee",
231
+ admin: "Administrator",
232
+ user: "User"
233
+ };
234
+ return names[role] || role;
235
+ }
236
+
237
+ // src/server/tiers.ts
238
+ var ALL_TIERS = [
239
+ "MasterClass",
240
+ "MasterClassLight",
241
+ "PremiumOnlineKurs",
242
+ "MasterOnlineKurs",
243
+ "OnlineKursPro",
244
+ "PremiumTraining",
245
+ "VIPTraining"
246
+ ];
247
+ var TIER_DISPLAY_NAMES = {
248
+ MasterClass: "MasterClass",
249
+ MasterClassLight: "MasterClass Light",
250
+ PremiumOnlineKurs: "Premium Online Kurs",
251
+ MasterOnlineKurs: "Master Online Kurs",
252
+ OnlineKursPro: "Online Kurs Pro",
253
+ PremiumTraining: "Premium Training",
254
+ VIPTraining: "VIP Training"
255
+ };
256
+ function hasAccess(userTier, requiredTiers) {
257
+ if (!userTier) return false;
258
+ return requiredTiers.includes(userTier);
259
+ }
260
+ function hasSubscription(userTier) {
261
+ return userTier !== null && userTier !== void 0;
262
+ }
263
+ function getTierDisplayName(tier) {
264
+ return TIER_DISPLAY_NAMES[tier] || tier;
265
+ }
266
+ function isSubscriptionExpired(subscription) {
267
+ if (!subscription.expirationDate) return false;
268
+ return /* @__PURE__ */ new Date() > subscription.expirationDate;
269
+ }
270
+ function isSubscriptionActive(subscription) {
271
+ return subscription.isActive && !isSubscriptionExpired(subscription);
272
+ }
273
+
274
+ export { ALL_TIERS, DEFAULT_MIDDLEWARE_CONFIG, DEFAULT_SESSION_CONFIG, ROLE_DESCRIPTIONS, TIER_DISPLAY_NAMES, createMiddlewareConfig, createSessionConfig, createSessionCookie, generateLoginCookieHeader, generateLogoutCookieHeader, getAdminAuth, getAdminFirestore, getRoleDisplayName, getSessionUser, getTierDisplayName, handleLogin, handleLogout, handleSession, hasAccess, hasRole, hasSubscription, initializeAdminApp, isAdmin, isOwner, isProtectedPath, isStaff, isSubscriptionActive, isSubscriptionExpired, revokeUserTokens, verifyAuth, verifyIdToken, verifySessionCookie };
275
+ //# sourceMappingURL=index.mjs.map
276
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/admin.ts","../../src/server/session.ts","../../src/server/apiRoutes.ts","../../src/server/middleware.ts","../../src/server/roles.ts","../../src/server/tiers.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import {\n initializeApp,\n getApps,\n cert,\n App,\n ServiceAccount,\n} from 'firebase-admin/app';\nimport { getAuth, Auth } from 'firebase-admin/auth';\nimport { getFirestore, Firestore } from 'firebase-admin/firestore';\n\nlet _adminApp: App | null = null;\nlet _adminAuth: Auth | null = null;\nlet _adminDb: Firestore | null = null;\n\n/**\n * Get Firebase Admin credentials from environment variables\n */\nfunction getAdminCredentials(): ServiceAccount {\n const projectId = process.env.FIREBASE_ADMIN_PROJECT_ID;\n const clientEmail = process.env.FIREBASE_ADMIN_CLIENT_EMAIL;\n const privateKey = process.env.FIREBASE_ADMIN_PRIVATE_KEY?.replace(/\\\\n/g, '\\n');\n\n if (!projectId || !clientEmail || !privateKey) {\n throw new Error(\n '[mw-core] Missing Firebase Admin credentials. ' +\n 'Set FIREBASE_ADMIN_PROJECT_ID, FIREBASE_ADMIN_CLIENT_EMAIL, and FIREBASE_ADMIN_PRIVATE_KEY environment variables.'\n );\n }\n\n return {\n projectId,\n clientEmail,\n privateKey,\n };\n}\n\n/**\n * Initialize Firebase Admin SDK (singleton)\n */\nexport function initializeAdminApp(): App {\n if (_adminApp) {\n return _adminApp;\n }\n\n const existingApps = getApps();\n if (existingApps.length > 0) {\n _adminApp = existingApps[0];\n return _adminApp;\n }\n\n const credentials = getAdminCredentials();\n _adminApp = initializeApp({\n credential: cert(credentials),\n });\n\n return _adminApp;\n}\n\n/**\n * Get Firebase Admin Auth instance\n */\nexport function getAdminAuth(): Auth {\n if (!_adminAuth) {\n _adminAuth = getAuth(initializeAdminApp());\n }\n return _adminAuth;\n}\n\n/**\n * Get Firebase Admin Firestore instance\n */\nexport function getAdminFirestore(): Firestore {\n if (!_adminDb) {\n _adminDb = getFirestore(initializeAdminApp());\n }\n return _adminDb;\n}\n\n// Re-export types\nexport type { App, Auth, Firestore };\n\n","import { getAdminAuth } from './admin';\nimport { DecodedIdToken } from 'firebase-admin/auth';\n\n/**\n * Session cookie configuration\n */\nexport interface SessionConfig {\n /** Cookie domain (e.g., '.marc-welti.ch') */\n domain: string;\n /** Session duration in milliseconds (default: 5 days) */\n expiresIn?: number;\n /** Cookie path (default: '/') */\n path?: string;\n /** Use secure cookies (default: true in production) */\n secure?: boolean;\n}\n\nconst DEFAULT_SESSION_DURATION = 60 * 60 * 24 * 5 * 1000; // 5 days in milliseconds\n\n/**\n * Create a session cookie from a Firebase ID token\n */\nexport async function createSessionCookie(\n idToken: string,\n config: SessionConfig\n): Promise<string> {\n const auth = getAdminAuth();\n const expiresIn = config.expiresIn || DEFAULT_SESSION_DURATION;\n\n // Create session cookie\n const sessionCookie = await auth.createSessionCookie(idToken, { expiresIn });\n\n return sessionCookie;\n}\n\n/**\n * Generate the Set-Cookie header string for login\n */\nexport function generateLoginCookieHeader(\n sessionCookie: string,\n config: SessionConfig\n): string {\n const expiresIn = config.expiresIn || DEFAULT_SESSION_DURATION;\n const maxAge = Math.floor(expiresIn / 1000); // Convert to seconds\n const secure = config.secure ?? process.env.NODE_ENV === 'production';\n const path = config.path || '/';\n\n const parts = [\n `session=${sessionCookie}`,\n `Domain=${config.domain}`,\n `Path=${path}`,\n 'HttpOnly',\n 'SameSite=Lax',\n `Max-Age=${maxAge}`,\n ];\n\n if (secure) {\n parts.push('Secure');\n }\n\n return parts.join('; ');\n}\n\n/**\n * Generate the Set-Cookie header string for logout\n */\nexport function generateLogoutCookieHeader(config: SessionConfig): string {\n const path = config.path || '/';\n const secure = config.secure ?? process.env.NODE_ENV === 'production';\n\n const parts = [\n 'session=',\n `Domain=${config.domain}`,\n `Path=${path}`,\n 'HttpOnly',\n 'SameSite=Lax',\n 'Max-Age=0',\n ];\n\n if (secure) {\n parts.push('Secure');\n }\n\n return parts.join('; ');\n}\n\n/**\n * Verify a session cookie and return decoded claims\n */\nexport async function verifySessionCookie(\n sessionCookie: string,\n checkRevoked: boolean = true\n): Promise<DecodedIdToken> {\n const auth = getAdminAuth();\n return auth.verifySessionCookie(sessionCookie, checkRevoked);\n}\n\n/**\n * Verify an ID token (for initial login)\n */\nexport async function verifyIdToken(idToken: string): Promise<DecodedIdToken> {\n const auth = getAdminAuth();\n return auth.verifyIdToken(idToken);\n}\n\n/**\n * Revoke all refresh tokens for a user (force logout everywhere)\n */\nexport async function revokeUserTokens(uid: string): Promise<void> {\n const auth = getAdminAuth();\n await auth.revokeRefreshTokens(uid);\n}\n\n/**\n * Session user data extracted from verified session\n */\nexport interface SessionUser {\n uid: string;\n email: string | undefined;\n emailVerified: boolean;\n displayName?: string;\n photoURL?: string;\n}\n\n/**\n * Verify session and return user data\n */\nexport async function getSessionUser(\n sessionCookie: string | undefined\n): Promise<SessionUser | null> {\n if (!sessionCookie) {\n return null;\n }\n\n try {\n const decodedClaims = await verifySessionCookie(sessionCookie);\n return {\n uid: decodedClaims.uid,\n email: decodedClaims.email,\n emailVerified: decodedClaims.email_verified || false,\n displayName: decodedClaims.name,\n photoURL: decodedClaims.picture,\n };\n } catch {\n return null;\n }\n}\n\n// Re-export types\nexport type { DecodedIdToken };\n\n","import {\n createSessionCookie,\n generateLoginCookieHeader,\n generateLogoutCookieHeader,\n verifySessionCookie,\n verifyIdToken,\n SessionConfig,\n SessionUser,\n} from './session';\n\n/**\n * Handle login: verify ID token and create session cookie\n * Returns the Set-Cookie header value\n */\nexport async function handleLogin(\n idToken: string,\n config: SessionConfig\n): Promise<string> {\n // Verify the ID token first\n await verifyIdToken(idToken);\n \n // Create session cookie\n const sessionCookie = await createSessionCookie(idToken, config);\n \n // Generate Set-Cookie header\n return generateLoginCookieHeader(sessionCookie, config);\n}\n\n/**\n * Handle logout: generate cookie clearing header\n * Returns the Set-Cookie header value\n */\nexport function handleLogout(config: SessionConfig): string {\n return generateLogoutCookieHeader(config);\n}\n\n/**\n * Handle session verification\n * Returns session user data or null if invalid\n */\nexport async function handleSession(\n sessionCookie: string | undefined\n): Promise<{ authenticated: boolean; user?: SessionUser }> {\n if (!sessionCookie) {\n return { authenticated: false };\n }\n\n try {\n const decodedClaims = await verifySessionCookie(sessionCookie);\n return {\n authenticated: true,\n user: {\n uid: decodedClaims.uid,\n email: decodedClaims.email,\n emailVerified: decodedClaims.email_verified || false,\n displayName: decodedClaims.name,\n photoURL: decodedClaims.picture,\n },\n };\n } catch {\n return { authenticated: false };\n }\n}\n\n/**\n * Default session config for marc-welti.ch\n */\nexport const DEFAULT_SESSION_CONFIG: SessionConfig = {\n domain: '.marc-welti.ch',\n expiresIn: 60 * 60 * 24 * 5 * 1000, // 5 days\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n};\n\n/**\n * Create session config with custom domain\n */\nexport function createSessionConfig(\n domain: string,\n options?: Partial<Omit<SessionConfig, 'domain'>>\n): SessionConfig {\n return {\n ...DEFAULT_SESSION_CONFIG,\n ...options,\n domain,\n };\n}\n\n","import { verifySessionCookie, SessionUser } from './session';\n\n/**\n * Result of middleware authentication check\n */\nexport interface AuthResult {\n authenticated: boolean;\n user: SessionUser | null;\n}\n\n/**\n * Verify session from cookie value\n * Use this in Next.js middleware or API routes\n */\nexport async function verifyAuth(\n sessionCookie: string | undefined\n): Promise<AuthResult> {\n if (!sessionCookie) {\n return { authenticated: false, user: null };\n }\n\n try {\n const decodedClaims = await verifySessionCookie(sessionCookie);\n return {\n authenticated: true,\n user: {\n uid: decodedClaims.uid,\n email: decodedClaims.email,\n emailVerified: decodedClaims.email_verified || false,\n displayName: decodedClaims.name,\n photoURL: decodedClaims.picture,\n },\n };\n } catch {\n return { authenticated: false, user: null };\n }\n}\n\n/**\n * Check if a path matches any of the protected patterns\n */\nexport function isProtectedPath(\n pathname: string,\n protectedPaths: string[]\n): boolean {\n return protectedPaths.some((path) => {\n // Exact match or starts with path\n return pathname === path || pathname.startsWith(`${path}/`);\n });\n}\n\n/**\n * Configuration for auth middleware\n */\nexport interface MiddlewareConfig {\n /** Paths that require authentication */\n protectedPaths: string[];\n /** Path to redirect unauthenticated users to */\n loginRedirectPath?: string;\n /** Cookie name (default: 'session') */\n cookieName?: string;\n}\n\n/**\n * Default middleware configuration\n */\nexport const DEFAULT_MIDDLEWARE_CONFIG: MiddlewareConfig = {\n protectedPaths: ['/account', '/dashboard'],\n loginRedirectPath: '/login-required',\n cookieName: 'session',\n};\n\n/**\n * Create middleware config with custom options\n */\nexport function createMiddlewareConfig(\n protectedPaths: string[],\n options?: Partial<Omit<MiddlewareConfig, 'protectedPaths'>>\n): MiddlewareConfig {\n return {\n ...DEFAULT_MIDDLEWARE_CONFIG,\n ...options,\n protectedPaths,\n };\n}\n\n","/**\n * User roles in the system\n */\nexport type UserRole = 'owner' | 'employee' | 'admin' | 'user';\n\n/**\n * Role hierarchy and permissions\n * \n * - owner: Marc only, full system access\n * - employee: Team members, limited to assigned customers\n * - admin: External developers/support, full technical access\n * - user: Customers, customer-facing features only\n */\nexport const ROLE_DESCRIPTIONS: Record<UserRole, string> = {\n owner: 'Full system access (Marc only)',\n employee: 'Limited access to assigned customers',\n admin: 'Technical support access',\n user: 'Customer access',\n};\n\n/**\n * Check if user has one of the required roles\n */\nexport function hasRole(\n userRole: UserRole | undefined | null,\n requiredRoles: UserRole[]\n): boolean {\n if (!userRole) return false;\n return requiredRoles.includes(userRole);\n}\n\n/**\n * Check if user is an admin-level role (owner, admin, or employee)\n */\nexport function isStaff(userRole: UserRole | undefined | null): boolean {\n return hasRole(userRole, ['owner', 'admin', 'employee']);\n}\n\n/**\n * Check if user has full admin access (owner or admin)\n */\nexport function isAdmin(userRole: UserRole | undefined | null): boolean {\n return hasRole(userRole, ['owner', 'admin']);\n}\n\n/**\n * Check if user is the owner\n */\nexport function isOwner(userRole: UserRole | undefined | null): boolean {\n return userRole === 'owner';\n}\n\n/**\n * Get role display name\n */\nexport function getRoleDisplayName(role: UserRole): string {\n const names: Record<UserRole, string> = {\n owner: 'Owner',\n employee: 'Employee',\n admin: 'Administrator',\n user: 'User',\n };\n return names[role] || role;\n}\n\n","/**\n * Subscription tiers (independent, not hierarchical)\n */\nexport type SubscriptionTier =\n | 'MasterClass'\n | 'MasterClassLight'\n | 'PremiumOnlineKurs'\n | 'MasterOnlineKurs'\n | 'OnlineKursPro'\n | 'PremiumTraining'\n | 'VIPTraining';\n\n/**\n * All available tiers\n */\nexport const ALL_TIERS: SubscriptionTier[] = [\n 'MasterClass',\n 'MasterClassLight',\n 'PremiumOnlineKurs',\n 'MasterOnlineKurs',\n 'OnlineKursPro',\n 'PremiumTraining',\n 'VIPTraining',\n];\n\n/**\n * Tier display names (internal only - not shown to customers)\n */\nexport const TIER_DISPLAY_NAMES: Record<SubscriptionTier, string> = {\n MasterClass: 'MasterClass',\n MasterClassLight: 'MasterClass Light',\n PremiumOnlineKurs: 'Premium Online Kurs',\n MasterOnlineKurs: 'Master Online Kurs',\n OnlineKursPro: 'Online Kurs Pro',\n PremiumTraining: 'Premium Training',\n VIPTraining: 'VIP Training',\n};\n\n/**\n * Check if user has access to a tier-gated feature\n * \n * @param userTier - The user's current subscription tier (or null if none)\n * @param requiredTiers - List of tiers that have access to the feature\n * @returns true if user's tier is in the required list\n */\nexport function hasAccess(\n userTier: SubscriptionTier | null | undefined,\n requiredTiers: SubscriptionTier[]\n): boolean {\n if (!userTier) return false;\n return requiredTiers.includes(userTier);\n}\n\n/**\n * Check if user has any active subscription\n */\nexport function hasSubscription(\n userTier: SubscriptionTier | null | undefined\n): boolean {\n return userTier !== null && userTier !== undefined;\n}\n\n/**\n * Get tier display name\n */\nexport function getTierDisplayName(tier: SubscriptionTier): string {\n return TIER_DISPLAY_NAMES[tier] || tier;\n}\n\n/**\n * Subscription data stored in Firestore\n */\nexport interface UserSubscription {\n tier: SubscriptionTier;\n startDate: Date;\n expirationDate?: Date;\n isActive: boolean;\n}\n\n/**\n * Check if subscription is expired\n */\nexport function isSubscriptionExpired(subscription: UserSubscription): boolean {\n if (!subscription.expirationDate) return false;\n return new Date() > subscription.expirationDate;\n}\n\n/**\n * Check if subscription is currently active\n */\nexport function isSubscriptionActive(subscription: UserSubscription): boolean {\n return subscription.isActive && !isSubscriptionExpired(subscription);\n}\n\n"]}