@htlkg/astro 0.0.1 → 0.0.2
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.
- package/dist/{chunk-WLOFOVCL.js → chunk-IWK5QCVD.js} +8 -2
- package/dist/chunk-IWK5QCVD.js.map +1 -0
- package/dist/htlkg/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/middleware/index.js +27 -28
- package/dist/middleware/index.js.map +1 -1
- package/package.json +24 -19
- package/src/htlkg/index.ts +10 -1
- package/src/layouts/AdminLayout.astro +103 -92
- package/src/middleware/auth.ts +42 -0
- package/src/middleware/route-guards.ts +4 -28
- package/src/vue-app-setup.ts +21 -28
- package/dist/chunk-WLOFOVCL.js.map +0 -1
|
@@ -50,6 +50,7 @@ declare module 'virtual:htlkg-config' {
|
|
|
50
50
|
`;
|
|
51
51
|
|
|
52
52
|
// src/htlkg/index.ts
|
|
53
|
+
import vueDevTools from "vite-plugin-vue-devtools";
|
|
53
54
|
var DEFAULT_ENV_VARS = [
|
|
54
55
|
"PUBLIC_COGNITO_USER_POOL_ID",
|
|
55
56
|
"PUBLIC_COGNITO_USER_POOL_CLIENT_ID"
|
|
@@ -117,9 +118,14 @@ ${missing.map((v) => ` - ${v}`).join("\n")}`
|
|
|
117
118
|
loginPage,
|
|
118
119
|
amplify || null
|
|
119
120
|
);
|
|
121
|
+
const vitePlugins = [virtualModulePlugin];
|
|
122
|
+
if (import.meta.env?.DEV !== false) {
|
|
123
|
+
vitePlugins.push(vueDevTools());
|
|
124
|
+
logger.info("Vue DevTools plugin enabled for development");
|
|
125
|
+
}
|
|
120
126
|
updateConfig({
|
|
121
127
|
vite: {
|
|
122
|
-
plugins:
|
|
128
|
+
plugins: vitePlugins
|
|
123
129
|
}
|
|
124
130
|
});
|
|
125
131
|
} catch (error) {
|
|
@@ -207,4 +213,4 @@ export {};
|
|
|
207
213
|
export {
|
|
208
214
|
htlkg
|
|
209
215
|
};
|
|
210
|
-
//# sourceMappingURL=chunk-
|
|
216
|
+
//# sourceMappingURL=chunk-IWK5QCVD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/htlkg/index.ts","../src/htlkg/virtual-modules.ts"],"sourcesContent":["/**\n * htlkg Astro Integration\n * \n * Provides zero-config setup for Hotelinking applications with:\n * - Tailwind CSS integration\n * - Vue 3 integration with Amplify setup\n * - Authentication middleware\n * - Route guards\n * - Login page generation\n */\n\nimport tailwind from '@astrojs/tailwind';\nimport vue from '@astrojs/vue';\nimport type { AstroIntegration } from 'astro';\nimport type { HtlkgIntegrationOptions } from './config.js';\nimport { createVirtualModulePlugin, virtualModuleTypes } from './virtual-modules.js';\nimport vueDevTools from 'vite-plugin-vue-devtools';\n\n/**\n * Default environment variables required for AWS Amplify authentication\n */\nconst DEFAULT_ENV_VARS = [\n\t'PUBLIC_COGNITO_USER_POOL_ID',\n\t'PUBLIC_COGNITO_USER_POOL_CLIENT_ID'\n];\n\n/**\n * htlkg Astro integration that provides zero-config authentication setup.\n *\n * This integration automatically:\n * - Includes Tailwind CSS integration (can be disabled)\n * - Includes Vue 3 integration with Amplify and Nanostores setup\n * - Injects authentication middleware for AWS Amplify\n * - Configures route guards based on declarative configuration\n * - Validates required environment variables (optional)\n * - Injects TypeScript types for Astro.locals.user\n * - Provides a default login page (optional)\n *\n * @param options - Configuration options for the integration\n * @returns Astro integration object or array of integrations\n *\n * @example\n * // astro.config.mjs\n * import { htlkg } from '@htlkg/astro';\n *\n * export default defineConfig({\n * integrations: [\n * htlkg({\n * tailwind: { configFile: './tailwind.config.mjs' },\n * auth: {\n * publicRoutes: ['/login', '/'],\n * adminRoutes: [/^\\/admin/],\n * loginUrl: '/login'\n * }\n * })\n * ]\n * });\n */\nexport function htlkg(\n\toptions: HtlkgIntegrationOptions = {},\n): AstroIntegration | AstroIntegration[] {\n\tconst {\n\t\tauth = {},\n\t\tloginPage = { path: '/login', title: 'Sign In', redirectUrl: '/admin' },\n\t\tvalidateEnv = true,\n\t\trequiredEnvVars = DEFAULT_ENV_VARS,\n\t\ttailwind: tailwindOptions,\n\t\tamplify,\n\t} = options;\n\n\tconst integrations: AstroIntegration[] = [];\n\n\t// Add Tailwind integration (enabled by default)\n\tif (tailwindOptions !== false) {\n\t\tconst tailwindConfig =\n\t\t\ttypeof tailwindOptions === 'object' ? tailwindOptions : undefined;\n\t\tintegrations.push(\n\t\t\ttailwind(tailwindConfig as Parameters<typeof tailwind>[0]),\n\t\t);\n\t}\n\n\t// Add Vue integration with Amplify setup entrypoint\n\t// Use package import specifier that Vite can resolve\n\tintegrations.push(\n\t\tvue({\n\t\t\tappEntrypoint: '@htlkg/astro/vue-app-setup',\n\t\t}),\n\t);\n\n\t// Add the main htlkg integration\n\tintegrations.push({\n\t\tname: '@htlkg/astro',\n\t\thooks: {\n\t\t\t'astro:config:setup': ({\n\t\t\t\tconfig,\n\t\t\t\tupdateConfig,\n\t\t\t\taddMiddleware,\n\t\t\t\tinjectRoute,\n\t\t\t\tlogger,\n\t\t\t}) => {\n\t\t\t\ttry {\n\t\t\t\t\t// 1. Verify Vue integration is present\n\t\t\t\t\tconst hasVue = config.integrations.some(\n\t\t\t\t\t\t(i) => i.name === '@astrojs/vue',\n\t\t\t\t\t);\n\t\t\t\t\tif (hasVue) {\n\t\t\t\t\t\tlogger.info('Vue integration configured with Amplify app setup');\n\t\t\t\t\t}\n\n\t\t\t\t\t// 2. Amplify will be configured by the middleware on first request\n\t\t\t\t\tif (amplify) {\n\t\t\t\t\t\tlogger.info('Amplify configuration provided - will be configured on first request');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.info('No Amplify configuration provided - will use environment variables');\n\t\t\t\t\t}\n\n\t\t\t\t\t// 3. Validate environment variables (only if not using amplify_outputs.json)\n\t\t\t\t\tif (validateEnv && !amplify) {\n\t\t\t\t\t\tconst missing = requiredEnvVars.filter(\n\t\t\t\t\t\t\t(varName) => !process.env[varName],\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (missing.length > 0) {\n\t\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t\t`Missing required environment variables: ${missing.join(', ')}\\nAuthentication may not work correctly. Please set these in your .env file:\\n${missing.map((v) => ` - ${v}`).join('\\n')}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlogger.info('All required environment variables are present');\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// 4. Create Vite virtual module plugin to pass configuration to middleware and pages\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst virtualModulePlugin = createVirtualModulePlugin(\n\t\t\t\t\t\t\tauth,\n\t\t\t\t\t\t\tloginPage,\n\t\t\t\t\t\t\tamplify || null\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tconst vitePlugins: any[] = [virtualModulePlugin];\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Add Vue DevTools plugin in development\n\t\t\t\t\t\tif (import.meta.env?.DEV !== false) {\n\t\t\t\t\t\t\tvitePlugins.push(vueDevTools());\n\t\t\t\t\t\t\tlogger.info('Vue DevTools plugin enabled for development');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tupdateConfig({\n\t\t\t\t\t\t\tvite: {\n\t\t\t\t\t\t\t\tplugins: vitePlugins,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst errorMsg =\n\t\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t\t`Failed to create virtual module for route configuration: ${errorMsg}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 5. Inject middleware\n\t\t\t\t\ttry {\n\t\t\t\t\t\taddMiddleware({\n\t\t\t\t\t\t\tentrypoint: '@htlkg/astro/middleware',\n\t\t\t\t\t\t\torder: 'pre',\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlogger.info('Authentication middleware injected successfully');\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst errorMsg =\n\t\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\t\tlogger.error(`Failed to inject middleware: ${errorMsg}`);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 6. Verify Vue app entrypoint is configured\n\t\t\t\t\tconst vueIntegrationIndex = config.integrations.findIndex(\n\t\t\t\t\t\t(i) => i.name === '@astrojs/vue',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (vueIntegrationIndex === -1) {\n\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t'@astrojs/vue integration not found.\\n' +\n\t\t\t\t\t\t\t\t'The htlkg integration should have added it automatically.\\n' +\n\t\t\t\t\t\t\t\t'If you see this warning, there may be an integration ordering issue.',\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.info('Vue app setup with Amplify configuration enabled');\n\t\t\t\t\t}\n\n\t\t\t\t\t// 7. Verify Tailwind integration\n\t\t\t\t\tconst hasTailwind = config.integrations.some(\n\t\t\t\t\t\t(i) =>\n\t\t\t\t\t\t\ti.name === '@astrojs/tailwind' || i.name === 'astro:tailwind',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (hasTailwind) {\n\t\t\t\t\t\tlogger.info('Tailwind CSS integration configured');\n\t\t\t\t\t}\n\n\t\t\t\t\t// 8. Inject login page route if enabled\n\t\t\t\t\tif (loginPage !== false) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst loginPath = loginPage.path || '/login';\n\t\t\t\t\t\t\tinjectRoute({\n\t\t\t\t\t\t\t\tpattern: loginPath,\n\t\t\t\t\t\t\t\tentrypoint: '@htlkg/astro/auth/LoginPage.astro',\n\t\t\t\t\t\t\t\tprerender: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tlogger.info(`Injected default login page at ${loginPath}`);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconst errorMsg =\n\t\t\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\t\t\tlogger.warn(`Failed to inject login page: ${errorMsg}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlogger.info('htlkg integration configured successfully');\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst errorMsg =\n\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`Failed to configure htlkg integration: ${errorMsg}`,\n\t\t\t\t\t);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t\t'astro:config:done': ({ injectTypes }) => {\n\t\t\t\t// Inject TypeScript types for Astro.locals and virtual module\n\t\t\t\tinjectTypes({\n\t\t\t\t\tfilename: 'htlkg.d.ts',\n\t\t\t\t\tcontent: `\nimport type { AuthUser } from '@htlkg/core/types';\n\ndeclare global {\n namespace App {\n interface Locals {\n user: AuthUser | null;\n }\n }\n}\n\n${virtualModuleTypes}\n\nexport {};\n`,\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t});\n\n\t// Return single integration or array based on what was added\n\treturn integrations.length === 1 ? integrations[0] : integrations;\n}\n","/**\n * Virtual module setup for htlkg integration\n * Creates Vite virtual modules to pass configuration to middleware and pages\n */\n\nimport type { RouteGuardConfig, LoginPageConfig, RoutePattern } from './config.js';\n\n/**\n * Serialize a route pattern (RegExp or string) to JavaScript code\n */\nfunction serializePattern(pattern: RegExp | string): string {\n\tif (pattern instanceof RegExp) {\n\t\treturn `new RegExp(${JSON.stringify(pattern.source)}, ${JSON.stringify(pattern.flags)})`;\n\t}\n\treturn JSON.stringify(pattern);\n}\n\n/**\n * Serialize an array of route patterns to JavaScript code\n */\nfunction serializePatterns(patterns: RoutePattern[]): string {\n\tif (!patterns || patterns.length === 0) return '[]';\n\treturn `[${patterns.map(serializePattern).join(', ')}]`;\n}\n\n/**\n * Create the virtual module plugin for Vite\n * This plugin provides configuration to middleware and pages at runtime\n */\nexport function createVirtualModulePlugin(\n\tauthConfig: RouteGuardConfig,\n\tloginPageConfig: LoginPageConfig | false | null,\n\tamplifyConfig: Record<string, unknown> | null\n) {\n\treturn {\n\t\tname: 'htlkg-config',\n\t\tresolveId(id: string) {\n\t\t\tif (id === 'virtual:htlkg-config') {\n\t\t\t\treturn '\\0virtual:htlkg-config';\n\t\t\t}\n\t\t},\n\t\tload(id: string) {\n\t\t\tif (id === '\\0virtual:htlkg-config') {\n\t\t\t\t// Serialize auth configuration with RegExp support\n\t\t\t\tconst serializedAuthConfig = `{\n\t\t\t\t\tpublicRoutes: ${serializePatterns(authConfig.publicRoutes || [])},\n\t\t\t\t\tauthenticatedRoutes: ${serializePatterns(authConfig.authenticatedRoutes || [])},\n\t\t\t\t\tadminRoutes: ${serializePatterns(authConfig.adminRoutes || [])},\n\t\t\t\t\tbrandRoutes: ${JSON.stringify(authConfig.brandRoutes || [])},\n\t\t\t\t\tloginUrl: ${JSON.stringify(authConfig.loginUrl || '/login')}\n\t\t\t\t}`;\n\n\t\t\t\tconst serializedAmplifyConfig = amplifyConfig\n\t\t\t\t\t? JSON.stringify(amplifyConfig)\n\t\t\t\t\t: 'null';\n\n\t\t\t\tconst serializedLoginPageConfig = loginPageConfig !== false && loginPageConfig !== null\n\t\t\t\t\t? JSON.stringify(loginPageConfig)\n\t\t\t\t\t: 'null';\n\n\t\t\t\treturn `export const routeGuardConfig = ${serializedAuthConfig};\nexport const loginPageConfig = ${serializedLoginPageConfig};\nexport const amplifyConfig = ${serializedAmplifyConfig};`;\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Type definitions for the virtual module\n * This should be injected into the project's type definitions\n */\nexport const virtualModuleTypes = `\ndeclare module 'virtual:htlkg-config' {\n import type { RouteGuardConfig, LoginPageConfig } from '@htlkg/astro/htlkg/config';\n \n export const routeGuardConfig: RouteGuardConfig;\n export const loginPageConfig: LoginPageConfig | null;\n export const amplifyConfig: Record<string, unknown> | null;\n}\n`;\n"],"mappings":"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","names":[]}
|
package/dist/htlkg/index.js
CHANGED
package/dist/index.js
CHANGED
package/dist/middleware/index.js
CHANGED
|
@@ -2,9 +2,32 @@
|
|
|
2
2
|
import { sequence } from "astro:middleware";
|
|
3
3
|
|
|
4
4
|
// src/middleware/auth.ts
|
|
5
|
+
import { Amplify } from "aws-amplify";
|
|
5
6
|
import { getUser } from "@htlkg/core/auth";
|
|
7
|
+
import { globalSettings } from "@htlkg/core/amplify-astro-adapter";
|
|
8
|
+
import { amplifyConfig } from "virtual:htlkg-config";
|
|
9
|
+
var amplifyConfigured = false;
|
|
10
|
+
function ensureAmplifyConfigured() {
|
|
11
|
+
if (amplifyConfigured) return;
|
|
12
|
+
try {
|
|
13
|
+
if (!amplifyConfig) {
|
|
14
|
+
console.warn("[htlkg Auth] No Amplify configuration provided");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
Amplify.configure(amplifyConfig, { ssr: true });
|
|
18
|
+
globalSettings.enableServerSideAuth();
|
|
19
|
+
const isSSL = typeof window !== "undefined" ? window.location.protocol === "https:" : process.env.NODE_ENV === "production";
|
|
20
|
+
globalSettings.setIsSSLOrigin(isSSL);
|
|
21
|
+
amplifyConfigured = true;
|
|
22
|
+
console.info("[htlkg Auth] Amplify configured for server-side auth");
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
25
|
+
console.error(`[htlkg Auth] Failed to configure Amplify: ${errorMsg}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
6
28
|
var authMiddleware = async (context, next) => {
|
|
7
29
|
const { locals } = context;
|
|
30
|
+
ensureAmplifyConfigured();
|
|
8
31
|
try {
|
|
9
32
|
const user = await getUser(context);
|
|
10
33
|
locals.user = user;
|
|
@@ -35,7 +58,7 @@ function matchesPattern(pathname, patterns) {
|
|
|
35
58
|
return pattern.test(pathname);
|
|
36
59
|
} catch (error) {
|
|
37
60
|
console.error(
|
|
38
|
-
"[htlkg
|
|
61
|
+
"[htlkg] Error matching route pattern:",
|
|
39
62
|
error instanceof Error ? error.message : "Unknown error"
|
|
40
63
|
);
|
|
41
64
|
return false;
|
|
@@ -54,20 +77,13 @@ var routeGuard = async (context, next) => {
|
|
|
54
77
|
} = config;
|
|
55
78
|
try {
|
|
56
79
|
if (matchesPattern(pathname, publicRoutes)) {
|
|
57
|
-
console.log(`[htlkg Route Guard] Public route: ${pathname}`);
|
|
58
80
|
return next();
|
|
59
81
|
}
|
|
60
82
|
const user = locals.user;
|
|
61
83
|
if (matchesPattern(pathname, adminRoutes)) {
|
|
62
84
|
if (!user || !user.isAdmin) {
|
|
63
|
-
console.log(
|
|
64
|
-
`[htlkg Route Guard] Admin access denied for ${pathname} - User: ${user ? "authenticated (non-admin)" : "not authenticated"}`
|
|
65
|
-
);
|
|
66
85
|
return redirect(`${loginUrl}?error=admin_required`);
|
|
67
86
|
}
|
|
68
|
-
console.log(
|
|
69
|
-
`[htlkg Route Guard] Admin access granted for ${pathname}`
|
|
70
|
-
);
|
|
71
87
|
return next();
|
|
72
88
|
}
|
|
73
89
|
for (const brandRoute of brandRoutes) {
|
|
@@ -76,25 +92,17 @@ var routeGuard = async (context, next) => {
|
|
|
76
92
|
if (match) {
|
|
77
93
|
const brandId = Number.parseInt(match[brandRoute.brandIdParam], 10);
|
|
78
94
|
if (Number.isNaN(brandId)) {
|
|
79
|
-
console.warn(
|
|
80
|
-
`[htlkg Route Guard] Invalid brandId extracted from ${pathname}`
|
|
81
|
-
);
|
|
95
|
+
console.warn(`[htlkg] Invalid brandId extracted from ${pathname}`);
|
|
82
96
|
return redirect(`${loginUrl}?error=invalid_brand`);
|
|
83
97
|
}
|
|
84
98
|
if (!user || !user.isAdmin && !user.brandIds.includes(brandId)) {
|
|
85
|
-
console.log(
|
|
86
|
-
`[htlkg Route Guard] Brand access denied for ${pathname} (brandId: ${brandId}) - User: ${user ? `authenticated (brandIds: ${user.brandIds.join(",")})` : "not authenticated"}`
|
|
87
|
-
);
|
|
88
99
|
return redirect(`${loginUrl}?error=access_denied`);
|
|
89
100
|
}
|
|
90
|
-
console.log(
|
|
91
|
-
`[htlkg Route Guard] Brand access granted for ${pathname} (brandId: ${brandId})`
|
|
92
|
-
);
|
|
93
101
|
return next();
|
|
94
102
|
}
|
|
95
103
|
} catch (error) {
|
|
96
104
|
console.error(
|
|
97
|
-
`[htlkg
|
|
105
|
+
`[htlkg] Error processing brand route for ${pathname}:`,
|
|
98
106
|
error instanceof Error ? error.message : "Unknown error"
|
|
99
107
|
);
|
|
100
108
|
return redirect(`${loginUrl}?error=access_denied`);
|
|
@@ -103,23 +111,14 @@ var routeGuard = async (context, next) => {
|
|
|
103
111
|
if (matchesPattern(pathname, authenticatedRoutes)) {
|
|
104
112
|
if (!user) {
|
|
105
113
|
const returnUrl = encodeURIComponent(pathname + url.search);
|
|
106
|
-
console.log(
|
|
107
|
-
`[htlkg Route Guard] Authentication required for ${pathname}, redirecting to login`
|
|
108
|
-
);
|
|
109
114
|
return redirect(`${loginUrl}?redirect=${returnUrl}`);
|
|
110
115
|
}
|
|
111
|
-
console.log(
|
|
112
|
-
`[htlkg Route Guard] Authenticated access granted for ${pathname}`
|
|
113
|
-
);
|
|
114
116
|
return next();
|
|
115
117
|
}
|
|
116
|
-
console.log(
|
|
117
|
-
`[htlkg Route Guard] Default access granted for ${pathname}`
|
|
118
|
-
);
|
|
119
118
|
return next();
|
|
120
119
|
} catch (error) {
|
|
121
120
|
console.error(
|
|
122
|
-
"[htlkg
|
|
121
|
+
"[htlkg] Unexpected error in route guard:",
|
|
123
122
|
error instanceof Error ? error.message : "Unknown error"
|
|
124
123
|
);
|
|
125
124
|
return redirect(`${loginUrl}?error=server_error`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/middleware/index.ts","../../src/middleware/auth.ts","../../src/middleware/route-guards.ts"],"sourcesContent":["/**\n * Middleware for @htlkg/astro\n * \n * This module exports:\n * - authMiddleware: Retrieves authenticated user and injects into locals\n * - routeGuard: Enforces access control based on route configuration\n * - onRequest: Composed middleware chain (auth + route guard)\n * - Helper functions: requireAuth, requireAdminAccess, requireBrandAccess\n */\n\nimport { sequence } from 'astro:middleware';\nimport { authMiddleware } from './auth.js';\nimport { routeGuard } from './route-guards.js';\n\n// Export individual middleware\nexport { authMiddleware } from './auth.js';\nexport {\n\trouteGuard,\n\trequireAuth,\n\trequireAdminAccess,\n\trequireBrandAccess,\n} from './route-guards.js';\n\n/**\n * Export composed middleware chain\n * Auth middleware runs first to populate locals.user\n * Route guard middleware runs second to enforce access control\n * \n * This is the default export that gets injected by the htlkg integration\n */\nexport const onRequest = sequence(authMiddleware, routeGuard);\n","/**\n * Authentication middleware for htlkg integration\n * \n * This middleware:\n * - Retrieves the authenticated user from AWS Amplify\n * - Injects the user into Astro.locals for use in pages and API routes\n * - Handles authentication errors gracefully\n */\n\nimport type { MiddlewareHandler } from 'astro';\nimport { getUser } from '@htlkg/core/auth';\n\n/**\n * Auth middleware - retrieves authenticated user and injects into locals\n * \n * This middleware runs on every request and attempts to get the current\n * authenticated user from AWS Amplify. If successful, the user is injected\n * into context.locals.user. If authentication fails, locals.user is set to null.\n * \n * @example\n * // In an Astro page\n * const { user } = Astro.locals;\n * if (user) {\n * console.log('Authenticated user:', user.email);\n * }\n */\nexport const authMiddleware: MiddlewareHandler = async (context, next) => {\n\tconst { locals } = context;\n\n\ttry {\n\t\tconst user = await getUser(context);\n\t\tlocals.user = user;\n\t} catch (error) {\n\t\t// Set user to null on any authentication error\n\t\tlocals.user = null;\n\n\t\t// Log error without exposing sensitive information\n\t\tif (error instanceof Error) {\n\t\t\t// Filter out sensitive data from error messages\n\t\t\tconst safeMessage = error.message\n\t\t\t\t.replace(/token[=:]\\s*[^\\s,}]+/gi, 'token=***')\n\t\t\t\t.replace(/key[=:]\\s*[^\\s,}]+/gi, 'key=***')\n\t\t\t\t.replace(/secret[=:]\\s*[^\\s,}]+/gi, 'secret=***')\n\t\t\t\t.replace(/password[=:]\\s*[^\\s,}]+/gi, 'password=***');\n\n\t\t\tconsole.error('[htlkg Auth] Authentication failed:', safeMessage);\n\t\t} else {\n\t\t\tconsole.error('[htlkg Auth] Authentication failed: Unknown error');\n\t\t}\n\t}\n\n\treturn next();\n};\n","/**\n * Route guard middleware for htlkg integration\n * \n * This middleware enforces access control based on route configuration:\n * - Public routes: accessible to everyone\n * - Authenticated routes: require any logged-in user\n * - Admin routes: require admin privileges\n * - Brand routes: require brand-specific access or admin privileges\n */\n\nimport type { MiddlewareHandler } from 'astro';\nimport type { RoutePattern, RouteGuardConfig } from '../htlkg/config.js';\n\n// Import configuration from virtual module\nimport { routeGuardConfig } from 'virtual:htlkg-config';\n\n// Type assertion for the imported config\nconst config = routeGuardConfig as RouteGuardConfig;\n\n/**\n * Helper: Check if pathname matches any of the provided patterns\n */\nfunction matchesPattern(pathname: string, patterns: RoutePattern[]): boolean {\n\treturn patterns.some((pattern) => {\n\t\ttry {\n\t\t\tif (typeof pattern === 'string') {\n\t\t\t\t// Special case: \"/\" only matches exactly\n\t\t\t\tif (pattern === '/') {\n\t\t\t\t\treturn pathname === '/';\n\t\t\t\t}\n\t\t\t\t// String pattern: exact match or starts with (for sub-routes)\n\t\t\t\treturn pathname === pattern || pathname.startsWith(pattern + '/');\n\t\t\t}\n\t\t\t// RegExp pattern: test against pathname\n\t\t\treturn pattern.test(pathname);\n\t\t} catch (error) {\n\t\t\t// Log pattern matching errors but don't block the request\n\t\t\tconsole.error(\n\t\t\t\t'[htlkg Route Guard] Error matching pattern:',\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t});\n}\n\n/**\n * Route guard middleware - protects routes based on configuration\n * \n * This middleware runs after authMiddleware and enforces access control\n * based on the route configuration provided to the htlkg integration.\n * \n * @example\n * // In astro.config.mjs\n * htlkg({\n * auth: {\n * publicRoutes: ['/login', '/'],\n * adminRoutes: [/^\\/admin/],\n * brandRoutes: [\n * { pattern: /^\\/brands\\/(\\d+)/, brandIdParam: 1 }\n * ],\n * loginUrl: '/login'\n * }\n * })\n */\nexport const routeGuard: MiddlewareHandler = async (context, next) => {\n\tconst { locals, url, redirect } = context;\n\tconst pathname = url.pathname;\n\n\tconst {\n\t\tpublicRoutes = [],\n\t\tauthenticatedRoutes = [],\n\t\tadminRoutes = [],\n\t\tbrandRoutes = [],\n\t\tloginUrl = '/login',\n\t} = config;\n\n\ttry {\n\t\t// Public routes - no auth required\n\t\tif (matchesPattern(pathname, publicRoutes)) {\n\t\t\tconsole.log(`[htlkg Route Guard] Public route: ${pathname}`);\n\t\t\treturn next();\n\t\t}\n\n\t\tconst user = locals.user;\n\n\t\t// Admin routes - require admin role\n\t\tif (matchesPattern(pathname, adminRoutes)) {\n\t\t\tif (!user || !user.isAdmin) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[htlkg Route Guard] Admin access denied for ${pathname} - User: ${user ? 'authenticated (non-admin)' : 'not authenticated'}`,\n\t\t\t\t);\n\t\t\t\treturn redirect(`${loginUrl}?error=admin_required`);\n\t\t\t}\n\t\t\tconsole.log(\n\t\t\t\t`[htlkg Route Guard] Admin access granted for ${pathname}`,\n\t\t\t);\n\t\t\treturn next();\n\t\t}\n\n\t\t// Brand routes - require brand access or admin\n\t\tfor (const brandRoute of brandRoutes) {\n\t\t\ttry {\n\t\t\t\tconst match = pathname.match(brandRoute.pattern);\n\t\t\t\tif (match) {\n\t\t\t\t\tconst brandId = Number.parseInt(match[brandRoute.brandIdParam], 10);\n\n\t\t\t\t\tif (Number.isNaN(brandId)) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`[htlkg Route Guard] Invalid brandId extracted from ${pathname}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn redirect(`${loginUrl}?error=invalid_brand`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!user || (!user.isAdmin && !user.brandIds.includes(brandId))) {\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`[htlkg Route Guard] Brand access denied for ${pathname} (brandId: ${brandId}) - User: ${user ? `authenticated (brandIds: ${user.brandIds.join(',')})` : 'not authenticated'}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn redirect(`${loginUrl}?error=access_denied`);\n\t\t\t\t\t}\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`[htlkg Route Guard] Brand access granted for ${pathname} (brandId: ${brandId})`,\n\t\t\t\t\t);\n\t\t\t\t\treturn next();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[htlkg Route Guard] Error processing brand route for ${pathname}:`,\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t\t\t);\n\t\t\t\t// Fail-safe: deny access on error\n\t\t\t\treturn redirect(`${loginUrl}?error=access_denied`);\n\t\t\t}\n\t\t}\n\n\t\t// Authenticated routes - require any logged-in user\n\t\tif (matchesPattern(pathname, authenticatedRoutes)) {\n\t\t\tif (!user) {\n\t\t\t\tconst returnUrl = encodeURIComponent(pathname + url.search);\n\t\t\t\tconsole.log(\n\t\t\t\t\t`[htlkg Route Guard] Authentication required for ${pathname}, redirecting to login`,\n\t\t\t\t);\n\t\t\t\treturn redirect(`${loginUrl}?redirect=${returnUrl}`);\n\t\t\t}\n\t\t\tconsole.log(\n\t\t\t\t`[htlkg Route Guard] Authenticated access granted for ${pathname}`,\n\t\t\t);\n\t\t\treturn next();\n\t\t}\n\n\t\t// Default: allow access\n\t\tconsole.log(\n\t\t\t`[htlkg Route Guard] Default access granted for ${pathname}`,\n\t\t);\n\t\treturn next();\n\t} catch (error) {\n\t\t// Catch-all error handler for route guard\n\t\tconsole.error(\n\t\t\t'[htlkg Route Guard] Unexpected error in route guard:',\n\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t);\n\t\t// Fail-safe: deny access and redirect to login\n\t\treturn redirect(`${loginUrl}?error=server_error`);\n\t}\n};\n\n/**\n * Helper functions for programmatic route protection in pages\n * These can be used in Astro pages for more fine-grained control\n */\n\n/**\n * Require authentication for a page\n * Redirects to login if user is not authenticated\n */\nexport async function requireAuth(context: any, loginUrl = '/login') {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\tconst currentUrl = context.url.pathname + context.url.search;\n\t\tconst encodedReturnUrl = encodeURIComponent(currentUrl);\n\t\treturn context.redirect(`${loginUrl}?redirect=${encodedReturnUrl}`);\n\t}\n\treturn user;\n}\n\n/**\n * Require admin access for a page\n * Redirects to login if user is not an admin\n */\nexport async function requireAdminAccess(context: any, loginUrl = '/login') {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\treturn context.redirect(`${loginUrl}?error=not_authenticated`);\n\t}\n\tif (!user.isAdmin) {\n\t\treturn context.redirect(`${loginUrl}?error=admin_required`);\n\t}\n\treturn user;\n}\n\n/**\n * Require brand access for a page\n * Redirects to login if user doesn't have access to the specified brand\n */\nexport async function requireBrandAccess(\n\tcontext: any,\n\tbrandId: number,\n\tloginUrl = '/login'\n) {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\treturn context.redirect(`${loginUrl}?error=not_authenticated`);\n\t}\n\tif (!user.isAdmin && !user.brandIds.includes(brandId)) {\n\t\treturn context.redirect(`${loginUrl}?error=access_denied`);\n\t}\n\treturn user;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/index.ts","../../src/middleware/auth.ts","../../src/middleware/route-guards.ts"],"sourcesContent":["/**\n * Middleware for @htlkg/astro\n * \n * This module exports:\n * - authMiddleware: Retrieves authenticated user and injects into locals\n * - routeGuard: Enforces access control based on route configuration\n * - onRequest: Composed middleware chain (auth + route guard)\n * - Helper functions: requireAuth, requireAdminAccess, requireBrandAccess\n */\n\nimport { sequence } from 'astro:middleware';\nimport { authMiddleware } from './auth.js';\nimport { routeGuard } from './route-guards.js';\n\n// Export individual middleware\nexport { authMiddleware } from './auth.js';\nexport {\n\trouteGuard,\n\trequireAuth,\n\trequireAdminAccess,\n\trequireBrandAccess,\n} from './route-guards.js';\n\n/**\n * Export composed middleware chain\n * Auth middleware runs first to populate locals.user\n * Route guard middleware runs second to enforce access control\n * \n * This is the default export that gets injected by the htlkg integration\n */\nexport const onRequest = sequence(authMiddleware, routeGuard);\n","/**\n * Authentication middleware for htlkg integration\n * \n * This middleware:\n * - Configures AWS Amplify for server-side auth\n * - Retrieves the authenticated user from AWS Amplify\n * - Injects the user into Astro.locals for use in pages and API routes\n * - Handles authentication errors gracefully\n */\n\nimport type { MiddlewareHandler } from 'astro';\nimport { Amplify } from 'aws-amplify';\nimport { getUser } from '@htlkg/core/auth';\nimport { globalSettings } from '@htlkg/core/amplify-astro-adapter';\nimport { amplifyConfig } from 'virtual:htlkg-config';\n\n// Track if Amplify has been configured\nlet amplifyConfigured = false;\n\n/**\n * Configure Amplify on first request\n */\nfunction ensureAmplifyConfigured(): void {\n\tif (amplifyConfigured) return;\n\n\ttry {\n\t\tif (!amplifyConfig) {\n\t\t\tconsole.warn('[htlkg Auth] No Amplify configuration provided');\n\t\t\treturn;\n\t\t}\n\n\t\t// Configure Amplify with SSR support\n\t\tAmplify.configure(amplifyConfig as any, { ssr: true });\n\t\t\n\t\t// Enable server-side auth in the adapter\n\t\tglobalSettings.enableServerSideAuth();\n\t\t\n\t\t// Check if we're on HTTPS (for secure cookies)\n\t\tconst isSSL = typeof window !== 'undefined' \n\t\t\t? window.location.protocol === 'https:' \n\t\t\t: process.env.NODE_ENV === 'production';\n\t\tglobalSettings.setIsSSLOrigin(isSSL);\n\t\t\n\t\tamplifyConfigured = true;\n\t\tconsole.info('[htlkg Auth] Amplify configured for server-side auth');\n\t} catch (error) {\n\t\tconst errorMsg = error instanceof Error ? error.message : 'Unknown error';\n\t\tconsole.error(`[htlkg Auth] Failed to configure Amplify: ${errorMsg}`);\n\t}\n}\n\n/**\n * Auth middleware - retrieves authenticated user and injects into locals\n * \n * This middleware runs on every request and attempts to get the current\n * authenticated user from AWS Amplify. If successful, the user is injected\n * into context.locals.user. If authentication fails, locals.user is set to null.\n * \n * @example\n * // In an Astro page\n * const { user } = Astro.locals;\n * if (user) {\n * console.log('Authenticated user:', user.email);\n * }\n */\nexport const authMiddleware: MiddlewareHandler = async (context, next) => {\n\tconst { locals } = context;\n\n\t// Ensure Amplify is configured before attempting auth\n\tensureAmplifyConfigured();\n\n\ttry {\n\t\tconst user = await getUser(context);\n\t\tlocals.user = user;\n\t} catch (error) {\n\t\t// Set user to null on any authentication error\n\t\tlocals.user = null;\n\n\t\t// Log error without exposing sensitive information\n\t\tif (error instanceof Error) {\n\t\t\t// Filter out sensitive data from error messages\n\t\t\tconst safeMessage = error.message\n\t\t\t\t.replace(/token[=:]\\s*[^\\s,}]+/gi, 'token=***')\n\t\t\t\t.replace(/key[=:]\\s*[^\\s,}]+/gi, 'key=***')\n\t\t\t\t.replace(/secret[=:]\\s*[^\\s,}]+/gi, 'secret=***')\n\t\t\t\t.replace(/password[=:]\\s*[^\\s,}]+/gi, 'password=***');\n\n\t\t\tconsole.error('[htlkg Auth] Authentication failed:', safeMessage);\n\t\t} else {\n\t\t\tconsole.error('[htlkg Auth] Authentication failed: Unknown error');\n\t\t}\n\t}\n\n\treturn next();\n};\n","/**\n * Route guard middleware for htlkg integration\n * \n * This middleware enforces access control based on route configuration:\n * - Public routes: accessible to everyone\n * - Authenticated routes: require any logged-in user\n * - Admin routes: require admin privileges\n * - Brand routes: require brand-specific access or admin privileges\n */\n\nimport type { MiddlewareHandler } from 'astro';\nimport type { RoutePattern, RouteGuardConfig } from '../htlkg/config.js';\n\n// Import configuration from virtual module\nimport { routeGuardConfig } from 'virtual:htlkg-config';\n\n// Type assertion for the imported config\nconst config = routeGuardConfig as RouteGuardConfig;\n\n/**\n * Helper: Check if pathname matches any of the provided patterns\n */\nfunction matchesPattern(pathname: string, patterns: RoutePattern[]): boolean {\n\treturn patterns.some((pattern) => {\n\t\ttry {\n\t\t\tif (typeof pattern === 'string') {\n\t\t\t\t// Special case: \"/\" only matches exactly\n\t\t\t\tif (pattern === '/') {\n\t\t\t\t\treturn pathname === '/';\n\t\t\t\t}\n\t\t\t\t// String pattern: exact match or starts with (for sub-routes)\n\t\t\t\treturn pathname === pattern || pathname.startsWith(pattern + '/');\n\t\t\t}\n\t\t\t// RegExp pattern: test against pathname\n\t\t\treturn pattern.test(pathname);\n\t\t} catch (error) {\n\t\t\t// Log pattern matching errors but don't block the request\n\t\t\tconsole.error(\n\t\t\t\t'[htlkg] Error matching route pattern:',\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t});\n}\n\n/**\n * Route guard middleware - protects routes based on configuration\n * \n * This middleware runs after authMiddleware and enforces access control\n * based on the route configuration provided to the htlkg integration.\n * \n * @example\n * // In astro.config.mjs\n * htlkg({\n * auth: {\n * publicRoutes: ['/login', '/'],\n * adminRoutes: [/^\\/admin/],\n * brandRoutes: [\n * { pattern: /^\\/brands\\/(\\d+)/, brandIdParam: 1 }\n * ],\n * loginUrl: '/login'\n * }\n * })\n */\nexport const routeGuard: MiddlewareHandler = async (context, next) => {\n\tconst { locals, url, redirect } = context;\n\tconst pathname = url.pathname;\n\n\tconst {\n\t\tpublicRoutes = [],\n\t\tauthenticatedRoutes = [],\n\t\tadminRoutes = [],\n\t\tbrandRoutes = [],\n\t\tloginUrl = '/login',\n\t} = config;\n\n\ttry {\n\t\t// Public routes - no auth required\n\t\tif (matchesPattern(pathname, publicRoutes)) {\n\t\t\treturn next();\n\t\t}\n\n\t\tconst user = locals.user;\n\n\t\t// Admin routes - require admin role\n\t\tif (matchesPattern(pathname, adminRoutes)) {\n\t\t\tif (!user || !user.isAdmin) {\n\t\t\t\treturn redirect(`${loginUrl}?error=admin_required`);\n\t\t\t}\n\t\t\treturn next();\n\t\t}\n\n\t\t// Brand routes - require brand access or admin\n\t\tfor (const brandRoute of brandRoutes) {\n\t\t\ttry {\n\t\t\t\tconst match = pathname.match(brandRoute.pattern);\n\t\t\t\tif (match) {\n\t\t\t\t\tconst brandId = Number.parseInt(match[brandRoute.brandIdParam], 10);\n\n\t\t\t\t\tif (Number.isNaN(brandId)) {\n\t\t\t\t\t\tconsole.warn(`[htlkg] Invalid brandId extracted from ${pathname}`);\n\t\t\t\t\t\treturn redirect(`${loginUrl}?error=invalid_brand`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!user || (!user.isAdmin && !user.brandIds.includes(brandId))) {\n\t\t\t\t\t\treturn redirect(`${loginUrl}?error=access_denied`);\n\t\t\t\t\t}\n\t\t\t\t\treturn next();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[htlkg] Error processing brand route for ${pathname}:`,\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t\t\t);\n\t\t\t\t// Fail-safe: deny access on error\n\t\t\t\treturn redirect(`${loginUrl}?error=access_denied`);\n\t\t\t}\n\t\t}\n\n\t\t// Authenticated routes - require any logged-in user\n\t\tif (matchesPattern(pathname, authenticatedRoutes)) {\n\t\t\tif (!user) {\n\t\t\t\tconst returnUrl = encodeURIComponent(pathname + url.search);\n\t\t\t\treturn redirect(`${loginUrl}?redirect=${returnUrl}`);\n\t\t\t}\n\t\t\treturn next();\n\t\t}\n\n\t\t// Default: allow access\n\t\treturn next();\n\t} catch (error) {\n\t\t// Catch-all error handler for route guard\n\t\tconsole.error(\n\t\t\t'[htlkg] Unexpected error in route guard:',\n\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t);\n\t\t// Fail-safe: deny access and redirect to login\n\t\treturn redirect(`${loginUrl}?error=server_error`);\n\t}\n};\n\n/**\n * Helper functions for programmatic route protection in pages\n * These can be used in Astro pages for more fine-grained control\n */\n\n/**\n * Require authentication for a page\n * Redirects to login if user is not authenticated\n */\nexport async function requireAuth(context: any, loginUrl = '/login') {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\tconst currentUrl = context.url.pathname + context.url.search;\n\t\tconst encodedReturnUrl = encodeURIComponent(currentUrl);\n\t\treturn context.redirect(`${loginUrl}?redirect=${encodedReturnUrl}`);\n\t}\n\treturn user;\n}\n\n/**\n * Require admin access for a page\n * Redirects to login if user is not an admin\n */\nexport async function requireAdminAccess(context: any, loginUrl = '/login') {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\treturn context.redirect(`${loginUrl}?error=not_authenticated`);\n\t}\n\tif (!user.isAdmin) {\n\t\treturn context.redirect(`${loginUrl}?error=admin_required`);\n\t}\n\treturn user;\n}\n\n/**\n * Require brand access for a page\n * Redirects to login if user doesn't have access to the specified brand\n */\nexport async function requireBrandAccess(\n\tcontext: any,\n\tbrandId: number,\n\tloginUrl = '/login'\n) {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\treturn context.redirect(`${loginUrl}?error=not_authenticated`);\n\t}\n\tif (!user.isAdmin && !user.brandIds.includes(brandId)) {\n\t\treturn context.redirect(`${loginUrl}?error=access_denied`);\n\t}\n\treturn user;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@htlkg/astro",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
"./vue-app-setup": "./src/vue-app-setup.ts",
|
|
11
11
|
"./auth/LoginPage.astro": "./src/auth/LoginPage.astro",
|
|
12
12
|
"./layouts": "./src/layouts/index.ts",
|
|
13
|
-
"./layouts
|
|
13
|
+
"./layouts/*.astro": "./src/layouts/*.astro",
|
|
14
14
|
"./patterns": "./src/patterns/index.ts",
|
|
15
15
|
"./patterns/admin": "./src/patterns/admin/index.ts",
|
|
16
|
-
"./patterns/admin
|
|
16
|
+
"./patterns/admin/*.astro": "./src/patterns/admin/*.astro",
|
|
17
17
|
"./patterns/brand": "./src/patterns/brand/index.ts",
|
|
18
|
-
"./patterns/brand
|
|
18
|
+
"./patterns/brand/*.astro": "./src/patterns/brand/*.astro",
|
|
19
19
|
"./components": "./src/components/index.ts",
|
|
20
|
-
"./components
|
|
20
|
+
"./components/*.astro": "./src/components/*.astro",
|
|
21
21
|
"./utils": "./dist/utils/index.js",
|
|
22
22
|
"./utils/*": "./dist/utils/*.js"
|
|
23
23
|
},
|
|
@@ -25,29 +25,34 @@
|
|
|
25
25
|
"dist",
|
|
26
26
|
"src"
|
|
27
27
|
],
|
|
28
|
-
"scripts": {
|
|
29
|
-
"build": "tsup",
|
|
30
|
-
"dev": "tsup --watch",
|
|
31
|
-
"test": "vitest run",
|
|
32
|
-
"test:watch": "vitest"
|
|
33
|
-
},
|
|
34
28
|
"dependencies": {
|
|
35
29
|
"@astrojs/tailwind": "^6.0.2",
|
|
36
30
|
"@astrojs/vue": "^5.0.0",
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
39
|
-
"@
|
|
31
|
+
"@hotelinking/ui": "^16.49.16",
|
|
32
|
+
"@nanostores/logger": "^1.0.0",
|
|
33
|
+
"@nanostores/vue": "^0.10.0",
|
|
40
34
|
"astro": "^5.14.7",
|
|
41
35
|
"aws-amplify": "^6.11.3",
|
|
36
|
+
"nanostores": "^0.11.3",
|
|
42
37
|
"tailwindcss": "^3.4.18",
|
|
43
|
-
"vue": "^3.5.22"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"access": "restricted"
|
|
38
|
+
"vue": "^3.5.22",
|
|
39
|
+
"@htlkg/core": "0.0.2",
|
|
40
|
+
"@htlkg/components": "0.0.2"
|
|
47
41
|
},
|
|
48
42
|
"devDependencies": {
|
|
43
|
+
"@vue/devtools-api": "^6.6.4",
|
|
49
44
|
"tsup": "^8.0.0",
|
|
50
45
|
"typescript": "^5.9.2",
|
|
46
|
+
"vite-plugin-vue-devtools": "^7.6.10",
|
|
51
47
|
"vitest": "^3.2.4"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "restricted"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"dev": "tsup --watch",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest"
|
|
52
57
|
}
|
|
53
|
-
}
|
|
58
|
+
}
|
package/src/htlkg/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import vue from '@astrojs/vue';
|
|
|
14
14
|
import type { AstroIntegration } from 'astro';
|
|
15
15
|
import type { HtlkgIntegrationOptions } from './config.js';
|
|
16
16
|
import { createVirtualModulePlugin, virtualModuleTypes } from './virtual-modules.js';
|
|
17
|
+
import vueDevTools from 'vite-plugin-vue-devtools';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Default environment variables required for AWS Amplify authentication
|
|
@@ -136,9 +137,17 @@ export function htlkg(
|
|
|
136
137
|
amplify || null
|
|
137
138
|
);
|
|
138
139
|
|
|
140
|
+
const vitePlugins: any[] = [virtualModulePlugin];
|
|
141
|
+
|
|
142
|
+
// Add Vue DevTools plugin in development
|
|
143
|
+
if (import.meta.env?.DEV !== false) {
|
|
144
|
+
vitePlugins.push(vueDevTools());
|
|
145
|
+
logger.info('Vue DevTools plugin enabled for development');
|
|
146
|
+
}
|
|
147
|
+
|
|
139
148
|
updateConfig({
|
|
140
149
|
vite: {
|
|
141
|
-
plugins:
|
|
150
|
+
plugins: vitePlugins,
|
|
142
151
|
},
|
|
143
152
|
});
|
|
144
153
|
} catch (error) {
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Admin Layout
|
|
4
4
|
*
|
|
5
|
-
* Admin layout using
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* This layout will be fully implemented in a future task.
|
|
9
|
-
* For now, it provides a basic structure.
|
|
5
|
+
* Admin layout using AdminWrapper component with uiWrapper, sidebar, and topbar.
|
|
6
|
+
* Simple admin layout without authentication - pages handle their own auth.
|
|
7
|
+
* Uses nanostores for user data (no props needed for user).
|
|
10
8
|
*/
|
|
11
9
|
|
|
10
|
+
import { AdminWrapper, setUser } from "@htlkg/components";
|
|
11
|
+
import { uiViewHeader as UiViewHeader } from "@hotelinking/ui";
|
|
12
|
+
|
|
12
13
|
interface User {
|
|
13
14
|
username?: string;
|
|
14
15
|
email?: string;
|
|
@@ -18,12 +19,6 @@ interface User {
|
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
interface BreadcrumbItem {
|
|
22
|
-
label: string;
|
|
23
|
-
routeName: string;
|
|
24
|
-
current?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
22
|
interface SidebarItem {
|
|
28
23
|
name: string;
|
|
29
24
|
routeName: string;
|
|
@@ -42,51 +37,86 @@ interface SelectItem {
|
|
|
42
37
|
id: string;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
interface
|
|
40
|
+
interface BreadcrumbPage {
|
|
41
|
+
name: string;
|
|
42
|
+
routeName: string;
|
|
43
|
+
current?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface HeaderProps {
|
|
46
47
|
title: string;
|
|
48
|
+
subtitle?: string;
|
|
47
49
|
description?: string;
|
|
50
|
+
breadcrumbs?: BreadcrumbPage[];
|
|
51
|
+
button?: {
|
|
52
|
+
text: string;
|
|
53
|
+
color?: "primary" | "secondary" | "red" | "yellow" | "green";
|
|
54
|
+
size?: "small" | "medium" | "big";
|
|
55
|
+
loading?: boolean;
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface SidebarProps {
|
|
61
|
+
logo: string;
|
|
62
|
+
title: string;
|
|
63
|
+
items: SidebarItem[];
|
|
48
64
|
currentPage?: string;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
sidebarItems?: SidebarItem[];
|
|
55
|
-
topbarActions?: TopbarAction[];
|
|
65
|
+
openByDefault?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface TopbarProps {
|
|
69
|
+
actions?: TopbarAction[];
|
|
56
70
|
selectItems?: SelectItem[];
|
|
57
71
|
selectedItem?: SelectItem;
|
|
58
|
-
sidebarOpenByDefault?: boolean;
|
|
59
72
|
}
|
|
60
73
|
|
|
74
|
+
interface Props {
|
|
75
|
+
header: HeaderProps;
|
|
76
|
+
sidebar: SidebarProps;
|
|
77
|
+
topbar?: TopbarProps;
|
|
78
|
+
user?: User;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { header, sidebar, topbar, user } = Astro.props;
|
|
82
|
+
|
|
83
|
+
// Extract header props
|
|
84
|
+
const { title, subtitle, description, breadcrumbs = [], button } = header;
|
|
85
|
+
|
|
86
|
+
// Extract sidebar props
|
|
61
87
|
const {
|
|
62
|
-
|
|
63
|
-
|
|
88
|
+
logo: sidebarLogo,
|
|
89
|
+
title: sidebarTitle,
|
|
90
|
+
items: sidebarItems,
|
|
64
91
|
currentPage = "dashboard",
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
topbarActions = [],
|
|
92
|
+
openByDefault: sidebarOpenByDefault = true,
|
|
93
|
+
} = sidebar;
|
|
94
|
+
|
|
95
|
+
// Extract topbar props
|
|
96
|
+
const {
|
|
97
|
+
actions: topbarActions = [],
|
|
72
98
|
selectItems = [],
|
|
73
99
|
selectedItem = { name: "", id: "" },
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const userData =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
} = topbar || {};
|
|
101
|
+
|
|
102
|
+
// Initialize user nanostore (server-side)
|
|
103
|
+
if (user) {
|
|
104
|
+
const userData = {
|
|
105
|
+
username: user.username || "Admin",
|
|
106
|
+
email: user.attributes?.email || user.email || "admin@example.com",
|
|
107
|
+
avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(user.username || "Admin")}&background=3B82F6&color=fff`,
|
|
108
|
+
isAdmin: user.isAdmin,
|
|
109
|
+
attributes: user.attributes,
|
|
110
|
+
};
|
|
111
|
+
setUser(userData);
|
|
112
|
+
} else {
|
|
113
|
+
setUser({
|
|
114
|
+
username: "Admin",
|
|
115
|
+
email: "admin@example.com",
|
|
116
|
+
avatar:
|
|
117
|
+
"https://ui-avatars.com/api/?name=Admin&background=3B82F6&color=fff",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
90
120
|
|
|
91
121
|
// Build products sidebar based on user admin status
|
|
92
122
|
const baseProducts = [
|
|
@@ -130,55 +160,36 @@ import { ClientRouter } from "astro:transitions";
|
|
|
130
160
|
</head>
|
|
131
161
|
<body>
|
|
132
162
|
<div id="app">
|
|
133
|
-
<!--
|
|
134
|
-
<!--
|
|
163
|
+
<!-- Admin Wrapper with uiWrapper, sidebar, and topbar -->
|
|
164
|
+
<!-- User data comes from nanostore (no props needed!) -->
|
|
165
|
+
<AdminWrapper
|
|
166
|
+
client:load
|
|
167
|
+
sidebarLogo={sidebarLogo}
|
|
168
|
+
currentPage={currentPage}
|
|
169
|
+
sidebarTitle={sidebarTitle}
|
|
170
|
+
sidebarItems={sidebarItems}
|
|
171
|
+
topbarActions={topbarActions}
|
|
172
|
+
selectItems={selectItems}
|
|
173
|
+
selectedItem={selectedItem}
|
|
174
|
+
productsSidebar={productsSidebar}
|
|
175
|
+
sidebarOpenByDefault={sidebarOpenByDefault}
|
|
176
|
+
>
|
|
177
|
+
<!-- View Header with breadcrumbs, title, and optional button -->
|
|
178
|
+
<UiViewHeader
|
|
179
|
+
client:load
|
|
180
|
+
pages={breadcrumbs}
|
|
181
|
+
title={title}
|
|
182
|
+
subtitle={subtitle}
|
|
183
|
+
description={description}
|
|
184
|
+
buttonText={button?.text}
|
|
185
|
+
buttonColor={button?.color || "primary"}
|
|
186
|
+
buttonSize={button?.size || "medium"}
|
|
187
|
+
buttonLoading={button?.loading || false}
|
|
188
|
+
buttonDisabled={button?.disabled || false}
|
|
189
|
+
/>
|
|
135
190
|
|
|
136
|
-
<!-- Breadcrumbs Section (if enabled) -->
|
|
137
|
-
{
|
|
138
|
-
showBreadcrumbs && breadcrumbs.length > 0 && (
|
|
139
|
-
<div class="breadcrumbs-section mb-6 px-6 pt-6">
|
|
140
|
-
<nav class="flex" aria-label="Breadcrumb">
|
|
141
|
-
{breadcrumbs.map((crumb, index) => (
|
|
142
|
-
<>
|
|
143
|
-
{crumb.routeName ? (
|
|
144
|
-
<a
|
|
145
|
-
href={crumb.routeName}
|
|
146
|
-
class="text-gray-500 hover:text-gray-700"
|
|
147
|
-
>
|
|
148
|
-
{crumb.label}
|
|
149
|
-
</a>
|
|
150
|
-
) : (
|
|
151
|
-
<span class="text-gray-900">{crumb.label}</span>
|
|
152
|
-
)}
|
|
153
|
-
{index < breadcrumbs.length - 1 && (
|
|
154
|
-
<span class="mx-2 text-gray-400">/</span>
|
|
155
|
-
)}
|
|
156
|
-
</>
|
|
157
|
-
))}
|
|
158
|
-
</nav>
|
|
159
|
-
</div>
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
<!-- Page Header -->
|
|
164
|
-
<div class="px-6 mb-6">
|
|
165
|
-
<div class="flex items-center justify-between">
|
|
166
|
-
<div>
|
|
167
|
-
<h1 class="text-2xl font-bold leading-7 sm:text-4xl sm:truncate">
|
|
168
|
-
{title}
|
|
169
|
-
</h1>
|
|
170
|
-
{description && <p class="text-gray-600 mt-2">{description}</p>}
|
|
171
|
-
</div>
|
|
172
|
-
<div class="flex items-center gap-3">
|
|
173
|
-
<slot name="actions" />
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
|
|
178
|
-
<!-- Page Content -->
|
|
179
|
-
<div class="px-6">
|
|
180
191
|
<slot />
|
|
181
|
-
</
|
|
192
|
+
</AdminWrapper>
|
|
182
193
|
</div>
|
|
183
194
|
</body>
|
|
184
195
|
</html>
|
package/src/middleware/auth.ts
CHANGED
|
@@ -2,13 +2,52 @@
|
|
|
2
2
|
* Authentication middleware for htlkg integration
|
|
3
3
|
*
|
|
4
4
|
* This middleware:
|
|
5
|
+
* - Configures AWS Amplify for server-side auth
|
|
5
6
|
* - Retrieves the authenticated user from AWS Amplify
|
|
6
7
|
* - Injects the user into Astro.locals for use in pages and API routes
|
|
7
8
|
* - Handles authentication errors gracefully
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import type { MiddlewareHandler } from 'astro';
|
|
12
|
+
import { Amplify } from 'aws-amplify';
|
|
11
13
|
import { getUser } from '@htlkg/core/auth';
|
|
14
|
+
import { globalSettings } from '@htlkg/core/amplify-astro-adapter';
|
|
15
|
+
import { amplifyConfig } from 'virtual:htlkg-config';
|
|
16
|
+
|
|
17
|
+
// Track if Amplify has been configured
|
|
18
|
+
let amplifyConfigured = false;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Configure Amplify on first request
|
|
22
|
+
*/
|
|
23
|
+
function ensureAmplifyConfigured(): void {
|
|
24
|
+
if (amplifyConfigured) return;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
if (!amplifyConfig) {
|
|
28
|
+
console.warn('[htlkg Auth] No Amplify configuration provided');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Configure Amplify with SSR support
|
|
33
|
+
Amplify.configure(amplifyConfig as any, { ssr: true });
|
|
34
|
+
|
|
35
|
+
// Enable server-side auth in the adapter
|
|
36
|
+
globalSettings.enableServerSideAuth();
|
|
37
|
+
|
|
38
|
+
// Check if we're on HTTPS (for secure cookies)
|
|
39
|
+
const isSSL = typeof window !== 'undefined'
|
|
40
|
+
? window.location.protocol === 'https:'
|
|
41
|
+
: process.env.NODE_ENV === 'production';
|
|
42
|
+
globalSettings.setIsSSLOrigin(isSSL);
|
|
43
|
+
|
|
44
|
+
amplifyConfigured = true;
|
|
45
|
+
console.info('[htlkg Auth] Amplify configured for server-side auth');
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
48
|
+
console.error(`[htlkg Auth] Failed to configure Amplify: ${errorMsg}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
12
51
|
|
|
13
52
|
/**
|
|
14
53
|
* Auth middleware - retrieves authenticated user and injects into locals
|
|
@@ -27,6 +66,9 @@ import { getUser } from '@htlkg/core/auth';
|
|
|
27
66
|
export const authMiddleware: MiddlewareHandler = async (context, next) => {
|
|
28
67
|
const { locals } = context;
|
|
29
68
|
|
|
69
|
+
// Ensure Amplify is configured before attempting auth
|
|
70
|
+
ensureAmplifyConfigured();
|
|
71
|
+
|
|
30
72
|
try {
|
|
31
73
|
const user = await getUser(context);
|
|
32
74
|
locals.user = user;
|
|
@@ -36,7 +36,7 @@ function matchesPattern(pathname: string, patterns: RoutePattern[]): boolean {
|
|
|
36
36
|
} catch (error) {
|
|
37
37
|
// Log pattern matching errors but don't block the request
|
|
38
38
|
console.error(
|
|
39
|
-
'[htlkg
|
|
39
|
+
'[htlkg] Error matching route pattern:',
|
|
40
40
|
error instanceof Error ? error.message : 'Unknown error',
|
|
41
41
|
);
|
|
42
42
|
return false;
|
|
@@ -78,7 +78,6 @@ export const routeGuard: MiddlewareHandler = async (context, next) => {
|
|
|
78
78
|
try {
|
|
79
79
|
// Public routes - no auth required
|
|
80
80
|
if (matchesPattern(pathname, publicRoutes)) {
|
|
81
|
-
console.log(`[htlkg Route Guard] Public route: ${pathname}`);
|
|
82
81
|
return next();
|
|
83
82
|
}
|
|
84
83
|
|
|
@@ -87,14 +86,8 @@ export const routeGuard: MiddlewareHandler = async (context, next) => {
|
|
|
87
86
|
// Admin routes - require admin role
|
|
88
87
|
if (matchesPattern(pathname, adminRoutes)) {
|
|
89
88
|
if (!user || !user.isAdmin) {
|
|
90
|
-
console.log(
|
|
91
|
-
`[htlkg Route Guard] Admin access denied for ${pathname} - User: ${user ? 'authenticated (non-admin)' : 'not authenticated'}`,
|
|
92
|
-
);
|
|
93
89
|
return redirect(`${loginUrl}?error=admin_required`);
|
|
94
90
|
}
|
|
95
|
-
console.log(
|
|
96
|
-
`[htlkg Route Guard] Admin access granted for ${pathname}`,
|
|
97
|
-
);
|
|
98
91
|
return next();
|
|
99
92
|
}
|
|
100
93
|
|
|
@@ -106,26 +99,18 @@ export const routeGuard: MiddlewareHandler = async (context, next) => {
|
|
|
106
99
|
const brandId = Number.parseInt(match[brandRoute.brandIdParam], 10);
|
|
107
100
|
|
|
108
101
|
if (Number.isNaN(brandId)) {
|
|
109
|
-
console.warn(
|
|
110
|
-
`[htlkg Route Guard] Invalid brandId extracted from ${pathname}`,
|
|
111
|
-
);
|
|
102
|
+
console.warn(`[htlkg] Invalid brandId extracted from ${pathname}`);
|
|
112
103
|
return redirect(`${loginUrl}?error=invalid_brand`);
|
|
113
104
|
}
|
|
114
105
|
|
|
115
106
|
if (!user || (!user.isAdmin && !user.brandIds.includes(brandId))) {
|
|
116
|
-
console.log(
|
|
117
|
-
`[htlkg Route Guard] Brand access denied for ${pathname} (brandId: ${brandId}) - User: ${user ? `authenticated (brandIds: ${user.brandIds.join(',')})` : 'not authenticated'}`,
|
|
118
|
-
);
|
|
119
107
|
return redirect(`${loginUrl}?error=access_denied`);
|
|
120
108
|
}
|
|
121
|
-
console.log(
|
|
122
|
-
`[htlkg Route Guard] Brand access granted for ${pathname} (brandId: ${brandId})`,
|
|
123
|
-
);
|
|
124
109
|
return next();
|
|
125
110
|
}
|
|
126
111
|
} catch (error) {
|
|
127
112
|
console.error(
|
|
128
|
-
`[htlkg
|
|
113
|
+
`[htlkg] Error processing brand route for ${pathname}:`,
|
|
129
114
|
error instanceof Error ? error.message : 'Unknown error',
|
|
130
115
|
);
|
|
131
116
|
// Fail-safe: deny access on error
|
|
@@ -137,26 +122,17 @@ export const routeGuard: MiddlewareHandler = async (context, next) => {
|
|
|
137
122
|
if (matchesPattern(pathname, authenticatedRoutes)) {
|
|
138
123
|
if (!user) {
|
|
139
124
|
const returnUrl = encodeURIComponent(pathname + url.search);
|
|
140
|
-
console.log(
|
|
141
|
-
`[htlkg Route Guard] Authentication required for ${pathname}, redirecting to login`,
|
|
142
|
-
);
|
|
143
125
|
return redirect(`${loginUrl}?redirect=${returnUrl}`);
|
|
144
126
|
}
|
|
145
|
-
console.log(
|
|
146
|
-
`[htlkg Route Guard] Authenticated access granted for ${pathname}`,
|
|
147
|
-
);
|
|
148
127
|
return next();
|
|
149
128
|
}
|
|
150
129
|
|
|
151
130
|
// Default: allow access
|
|
152
|
-
console.log(
|
|
153
|
-
`[htlkg Route Guard] Default access granted for ${pathname}`,
|
|
154
|
-
);
|
|
155
131
|
return next();
|
|
156
132
|
} catch (error) {
|
|
157
133
|
// Catch-all error handler for route guard
|
|
158
134
|
console.error(
|
|
159
|
-
'[htlkg
|
|
135
|
+
'[htlkg] Unexpected error in route guard:',
|
|
160
136
|
error instanceof Error ? error.message : 'Unknown error',
|
|
161
137
|
);
|
|
162
138
|
// Fail-safe: deny access and redirect to login
|
package/src/vue-app-setup.ts
CHANGED
|
@@ -3,60 +3,59 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This file is automatically loaded by the Vue integration when vueAppSetup is enabled.
|
|
5
5
|
* It configures AWS Amplify for client-side authentication in all Vue components.
|
|
6
|
+
* It also sets up nanostores devtools integration for Vue DevTools.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
/// <reference types="vite/client" />
|
|
9
10
|
|
|
10
|
-
console.log("[htlkg Vue Setup] MODULE LOADED - vue-app-setup.ts is being imported");
|
|
11
|
-
|
|
12
11
|
import { amplifyConfig } from "virtual:htlkg-config";
|
|
13
12
|
import type { App } from "vue";
|
|
14
13
|
import type { ResourcesConfig } from "aws-amplify";
|
|
15
14
|
import { Amplify } from "aws-amplify";
|
|
16
15
|
|
|
17
|
-
console.log("[htlkg Vue Setup] MODULE LEVEL - After imports, amplifyConfig:", amplifyConfig);
|
|
18
|
-
|
|
19
16
|
/**
|
|
20
17
|
* Setup function called by Astro's Vue integration
|
|
21
|
-
* Configures Amplify for client-side authentication
|
|
18
|
+
* Configures Amplify for client-side authentication and nanostores devtools
|
|
22
19
|
*/
|
|
23
20
|
export default function setupVueApp(app: App): void {
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// Setup nanostores devtools in development
|
|
22
|
+
// The devtools plugin will automatically detect stores used in components
|
|
23
|
+
if (import.meta.env.DEV && typeof window !== 'undefined') {
|
|
24
|
+
try {
|
|
25
|
+
// Dynamically import devtools plugin (only in browser)
|
|
26
|
+
import('@nanostores/vue/devtools').then(({ devtools }) => {
|
|
27
|
+
// Install devtools plugin - it will detect stores automatically
|
|
28
|
+
app.use(devtools, {});
|
|
29
|
+
}).catch(() => {
|
|
30
|
+
// Silently ignore - devtools are optional
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
// Silently ignore - devtools are optional
|
|
34
|
+
}
|
|
35
|
+
}
|
|
26
36
|
|
|
37
|
+
// Setup Amplify
|
|
27
38
|
try {
|
|
28
39
|
if (!amplifyConfig) {
|
|
29
|
-
console.warn("[htlkg
|
|
40
|
+
console.warn("[htlkg] No Amplify configuration provided");
|
|
30
41
|
return;
|
|
31
42
|
}
|
|
32
43
|
|
|
33
|
-
console.log("[htlkg Vue Setup] amplifyConfig keys:", Object.keys(amplifyConfig));
|
|
34
|
-
|
|
35
44
|
// Check if this is a full amplify_outputs.json config
|
|
36
45
|
if (
|
|
37
46
|
"auth" in amplifyConfig ||
|
|
38
47
|
"data" in amplifyConfig ||
|
|
39
48
|
"storage" in amplifyConfig
|
|
40
49
|
) {
|
|
41
|
-
console.log("[htlkg Vue Setup] Detected amplify_outputs.json format");
|
|
42
50
|
Amplify.configure(amplifyConfig as ResourcesConfig, { ssr: true });
|
|
43
|
-
console.info(
|
|
44
|
-
"[htlkg Vue Setup] ✅ Configured Amplify with amplify_outputs.json",
|
|
45
|
-
);
|
|
46
|
-
console.log("[htlkg Vue Setup] Amplify config:", Amplify.getConfig());
|
|
47
51
|
} else {
|
|
48
52
|
// Legacy individual config properties
|
|
49
|
-
console.log("[htlkg Vue Setup] Detected legacy config format");
|
|
50
53
|
const { userPoolId, userPoolClientId, region } = amplifyConfig as {
|
|
51
54
|
userPoolId?: string;
|
|
52
55
|
userPoolClientId?: string;
|
|
53
56
|
region?: string;
|
|
54
57
|
};
|
|
55
58
|
|
|
56
|
-
console.log("[htlkg Vue Setup] userPoolId:", userPoolId ? "present" : "missing");
|
|
57
|
-
console.log("[htlkg Vue Setup] userPoolClientId:", userPoolClientId ? "present" : "missing");
|
|
58
|
-
console.log("[htlkg Vue Setup] region:", region || "not specified");
|
|
59
|
-
|
|
60
59
|
if (userPoolId && userPoolClientId) {
|
|
61
60
|
const config: ResourcesConfig = {
|
|
62
61
|
Auth: {
|
|
@@ -68,21 +67,15 @@ export default function setupVueApp(app: App): void {
|
|
|
68
67
|
},
|
|
69
68
|
};
|
|
70
69
|
|
|
71
|
-
console.log("[htlkg Vue Setup] Configuring Amplify with:", JSON.stringify(config, null, 2));
|
|
72
70
|
Amplify.configure(config, { ssr: true });
|
|
73
|
-
console.info(
|
|
74
|
-
"[htlkg Vue Setup] ✅ Configured Amplify with legacy config properties",
|
|
75
|
-
);
|
|
76
|
-
console.log("[htlkg Vue Setup] Amplify config:", Amplify.getConfig());
|
|
77
71
|
} else {
|
|
78
72
|
console.error(
|
|
79
|
-
"[htlkg
|
|
73
|
+
"[htlkg] Missing required Amplify configuration (userPoolId, userPoolClientId)",
|
|
80
74
|
);
|
|
81
75
|
}
|
|
82
76
|
}
|
|
83
77
|
} catch (error) {
|
|
84
|
-
console.error("[htlkg
|
|
85
|
-
console.error("[htlkg Vue Setup] Error details:", error instanceof Error ? error.message : error);
|
|
78
|
+
console.error("[htlkg] Failed to setup Vue app:", error);
|
|
86
79
|
// Don't throw - allow app to continue even if Amplify setup fails
|
|
87
80
|
}
|
|
88
81
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/htlkg/index.ts","../src/htlkg/virtual-modules.ts"],"sourcesContent":["/**\n * htlkg Astro Integration\n * \n * Provides zero-config setup for Hotelinking applications with:\n * - Tailwind CSS integration\n * - Vue 3 integration with Amplify setup\n * - Authentication middleware\n * - Route guards\n * - Login page generation\n */\n\nimport tailwind from '@astrojs/tailwind';\nimport vue from '@astrojs/vue';\nimport type { AstroIntegration } from 'astro';\nimport type { HtlkgIntegrationOptions } from './config.js';\nimport { createVirtualModulePlugin, virtualModuleTypes } from './virtual-modules.js';\n\n/**\n * Default environment variables required for AWS Amplify authentication\n */\nconst DEFAULT_ENV_VARS = [\n\t'PUBLIC_COGNITO_USER_POOL_ID',\n\t'PUBLIC_COGNITO_USER_POOL_CLIENT_ID'\n];\n\n/**\n * htlkg Astro integration that provides zero-config authentication setup.\n *\n * This integration automatically:\n * - Includes Tailwind CSS integration (can be disabled)\n * - Includes Vue 3 integration with Amplify and Nanostores setup\n * - Injects authentication middleware for AWS Amplify\n * - Configures route guards based on declarative configuration\n * - Validates required environment variables (optional)\n * - Injects TypeScript types for Astro.locals.user\n * - Provides a default login page (optional)\n *\n * @param options - Configuration options for the integration\n * @returns Astro integration object or array of integrations\n *\n * @example\n * // astro.config.mjs\n * import { htlkg } from '@htlkg/astro';\n *\n * export default defineConfig({\n * integrations: [\n * htlkg({\n * tailwind: { configFile: './tailwind.config.mjs' },\n * auth: {\n * publicRoutes: ['/login', '/'],\n * adminRoutes: [/^\\/admin/],\n * loginUrl: '/login'\n * }\n * })\n * ]\n * });\n */\nexport function htlkg(\n\toptions: HtlkgIntegrationOptions = {},\n): AstroIntegration | AstroIntegration[] {\n\tconst {\n\t\tauth = {},\n\t\tloginPage = { path: '/login', title: 'Sign In', redirectUrl: '/admin' },\n\t\tvalidateEnv = true,\n\t\trequiredEnvVars = DEFAULT_ENV_VARS,\n\t\ttailwind: tailwindOptions,\n\t\tamplify,\n\t} = options;\n\n\tconst integrations: AstroIntegration[] = [];\n\n\t// Add Tailwind integration (enabled by default)\n\tif (tailwindOptions !== false) {\n\t\tconst tailwindConfig =\n\t\t\ttypeof tailwindOptions === 'object' ? tailwindOptions : undefined;\n\t\tintegrations.push(\n\t\t\ttailwind(tailwindConfig as Parameters<typeof tailwind>[0]),\n\t\t);\n\t}\n\n\t// Add Vue integration with Amplify setup entrypoint\n\t// Use package import specifier that Vite can resolve\n\tintegrations.push(\n\t\tvue({\n\t\t\tappEntrypoint: '@htlkg/astro/vue-app-setup',\n\t\t}),\n\t);\n\n\t// Add the main htlkg integration\n\tintegrations.push({\n\t\tname: '@htlkg/astro',\n\t\thooks: {\n\t\t\t'astro:config:setup': ({\n\t\t\t\tconfig,\n\t\t\t\tupdateConfig,\n\t\t\t\taddMiddleware,\n\t\t\t\tinjectRoute,\n\t\t\t\tlogger,\n\t\t\t}) => {\n\t\t\t\ttry {\n\t\t\t\t\t// 1. Verify Vue integration is present\n\t\t\t\t\tconst hasVue = config.integrations.some(\n\t\t\t\t\t\t(i) => i.name === '@astrojs/vue',\n\t\t\t\t\t);\n\t\t\t\t\tif (hasVue) {\n\t\t\t\t\t\tlogger.info('Vue integration configured with Amplify app setup');\n\t\t\t\t\t}\n\n\t\t\t\t\t// 2. Amplify will be configured by the middleware on first request\n\t\t\t\t\tif (amplify) {\n\t\t\t\t\t\tlogger.info('Amplify configuration provided - will be configured on first request');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.info('No Amplify configuration provided - will use environment variables');\n\t\t\t\t\t}\n\n\t\t\t\t\t// 3. Validate environment variables (only if not using amplify_outputs.json)\n\t\t\t\t\tif (validateEnv && !amplify) {\n\t\t\t\t\t\tconst missing = requiredEnvVars.filter(\n\t\t\t\t\t\t\t(varName) => !process.env[varName],\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (missing.length > 0) {\n\t\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t\t`Missing required environment variables: ${missing.join(', ')}\\nAuthentication may not work correctly. Please set these in your .env file:\\n${missing.map((v) => ` - ${v}`).join('\\n')}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlogger.info('All required environment variables are present');\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// 4. Create Vite virtual module plugin to pass configuration to middleware and pages\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst virtualModulePlugin = createVirtualModulePlugin(\n\t\t\t\t\t\t\tauth,\n\t\t\t\t\t\t\tloginPage,\n\t\t\t\t\t\t\tamplify || null\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tupdateConfig({\n\t\t\t\t\t\t\tvite: {\n\t\t\t\t\t\t\t\tplugins: [virtualModulePlugin],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst errorMsg =\n\t\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t\t`Failed to create virtual module for route configuration: ${errorMsg}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 5. Inject middleware\n\t\t\t\t\ttry {\n\t\t\t\t\t\taddMiddleware({\n\t\t\t\t\t\t\tentrypoint: '@htlkg/astro/middleware',\n\t\t\t\t\t\t\torder: 'pre',\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlogger.info('Authentication middleware injected successfully');\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst errorMsg =\n\t\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\t\tlogger.error(`Failed to inject middleware: ${errorMsg}`);\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 6. Verify Vue app entrypoint is configured\n\t\t\t\t\tconst vueIntegrationIndex = config.integrations.findIndex(\n\t\t\t\t\t\t(i) => i.name === '@astrojs/vue',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (vueIntegrationIndex === -1) {\n\t\t\t\t\t\tlogger.warn(\n\t\t\t\t\t\t\t'@astrojs/vue integration not found.\\n' +\n\t\t\t\t\t\t\t\t'The htlkg integration should have added it automatically.\\n' +\n\t\t\t\t\t\t\t\t'If you see this warning, there may be an integration ordering issue.',\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger.info('Vue app setup with Amplify configuration enabled');\n\t\t\t\t\t}\n\n\t\t\t\t\t// 7. Verify Tailwind integration\n\t\t\t\t\tconst hasTailwind = config.integrations.some(\n\t\t\t\t\t\t(i) =>\n\t\t\t\t\t\t\ti.name === '@astrojs/tailwind' || i.name === 'astro:tailwind',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (hasTailwind) {\n\t\t\t\t\t\tlogger.info('Tailwind CSS integration configured');\n\t\t\t\t\t}\n\n\t\t\t\t\t// 8. Inject login page route if enabled\n\t\t\t\t\tif (loginPage !== false) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst loginPath = loginPage.path || '/login';\n\t\t\t\t\t\t\tinjectRoute({\n\t\t\t\t\t\t\t\tpattern: loginPath,\n\t\t\t\t\t\t\t\tentrypoint: '@htlkg/astro/auth/LoginPage.astro',\n\t\t\t\t\t\t\t\tprerender: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tlogger.info(`Injected default login page at ${loginPath}`);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconst errorMsg =\n\t\t\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\t\t\tlogger.warn(`Failed to inject login page: ${errorMsg}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlogger.info('htlkg integration configured successfully');\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst errorMsg =\n\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`Failed to configure htlkg integration: ${errorMsg}`,\n\t\t\t\t\t);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t\t'astro:config:done': ({ injectTypes }) => {\n\t\t\t\t// Inject TypeScript types for Astro.locals and virtual module\n\t\t\t\tinjectTypes({\n\t\t\t\t\tfilename: 'htlkg.d.ts',\n\t\t\t\t\tcontent: `\nimport type { AuthUser } from '@htlkg/core/types';\n\ndeclare global {\n namespace App {\n interface Locals {\n user: AuthUser | null;\n }\n }\n}\n\n${virtualModuleTypes}\n\nexport {};\n`,\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t});\n\n\t// Return single integration or array based on what was added\n\treturn integrations.length === 1 ? integrations[0] : integrations;\n}\n","/**\n * Virtual module setup for htlkg integration\n * Creates Vite virtual modules to pass configuration to middleware and pages\n */\n\nimport type { RouteGuardConfig, LoginPageConfig, RoutePattern } from './config.js';\n\n/**\n * Serialize a route pattern (RegExp or string) to JavaScript code\n */\nfunction serializePattern(pattern: RegExp | string): string {\n\tif (pattern instanceof RegExp) {\n\t\treturn `new RegExp(${JSON.stringify(pattern.source)}, ${JSON.stringify(pattern.flags)})`;\n\t}\n\treturn JSON.stringify(pattern);\n}\n\n/**\n * Serialize an array of route patterns to JavaScript code\n */\nfunction serializePatterns(patterns: RoutePattern[]): string {\n\tif (!patterns || patterns.length === 0) return '[]';\n\treturn `[${patterns.map(serializePattern).join(', ')}]`;\n}\n\n/**\n * Create the virtual module plugin for Vite\n * This plugin provides configuration to middleware and pages at runtime\n */\nexport function createVirtualModulePlugin(\n\tauthConfig: RouteGuardConfig,\n\tloginPageConfig: LoginPageConfig | false | null,\n\tamplifyConfig: Record<string, unknown> | null\n) {\n\treturn {\n\t\tname: 'htlkg-config',\n\t\tresolveId(id: string) {\n\t\t\tif (id === 'virtual:htlkg-config') {\n\t\t\t\treturn '\\0virtual:htlkg-config';\n\t\t\t}\n\t\t},\n\t\tload(id: string) {\n\t\t\tif (id === '\\0virtual:htlkg-config') {\n\t\t\t\t// Serialize auth configuration with RegExp support\n\t\t\t\tconst serializedAuthConfig = `{\n\t\t\t\t\tpublicRoutes: ${serializePatterns(authConfig.publicRoutes || [])},\n\t\t\t\t\tauthenticatedRoutes: ${serializePatterns(authConfig.authenticatedRoutes || [])},\n\t\t\t\t\tadminRoutes: ${serializePatterns(authConfig.adminRoutes || [])},\n\t\t\t\t\tbrandRoutes: ${JSON.stringify(authConfig.brandRoutes || [])},\n\t\t\t\t\tloginUrl: ${JSON.stringify(authConfig.loginUrl || '/login')}\n\t\t\t\t}`;\n\n\t\t\t\tconst serializedAmplifyConfig = amplifyConfig\n\t\t\t\t\t? JSON.stringify(amplifyConfig)\n\t\t\t\t\t: 'null';\n\n\t\t\t\tconst serializedLoginPageConfig = loginPageConfig !== false && loginPageConfig !== null\n\t\t\t\t\t? JSON.stringify(loginPageConfig)\n\t\t\t\t\t: 'null';\n\n\t\t\t\treturn `export const routeGuardConfig = ${serializedAuthConfig};\nexport const loginPageConfig = ${serializedLoginPageConfig};\nexport const amplifyConfig = ${serializedAmplifyConfig};`;\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Type definitions for the virtual module\n * This should be injected into the project's type definitions\n */\nexport const virtualModuleTypes = `\ndeclare module 'virtual:htlkg-config' {\n import type { RouteGuardConfig, LoginPageConfig } from '@htlkg/astro/htlkg/config';\n \n export const routeGuardConfig: RouteGuardConfig;\n export const loginPageConfig: LoginPageConfig | null;\n export const amplifyConfig: Record<string, unknown> | null;\n}\n`;\n"],"mappings":"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","names":[]}
|