@eventcatalog/core 2.42.9 → 2.43.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 (58) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/catalog-to-astro-content-directory.js +2 -2
  6. package/dist/{chunk-4A2C2PVU.js → chunk-4DXH4NAT.js} +1 -1
  7. package/dist/{chunk-OQA3PAMR.js → chunk-IZ7E57FH.js} +1 -1
  8. package/dist/{chunk-HDG7YSFG.js → chunk-LUUBKWYP.js} +8 -0
  9. package/dist/{chunk-ISA6KNLN.js → chunk-MXNHNGRS.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.auth.cjs +18 -0
  13. package/dist/eventcatalog.auth.d.cts +18 -0
  14. package/dist/eventcatalog.auth.d.ts +18 -0
  15. package/dist/eventcatalog.auth.js +0 -0
  16. package/dist/eventcatalog.cjs +50 -23
  17. package/dist/eventcatalog.js +29 -8
  18. package/dist/features.cjs +9 -0
  19. package/dist/features.d.cts +2 -1
  20. package/dist/features.d.ts +2 -1
  21. package/dist/features.js +3 -1
  22. package/dist/watcher.js +1 -1
  23. package/eventcatalog/astro.config.mjs +7 -3
  24. package/eventcatalog/auth.config.ts +142 -0
  25. package/eventcatalog/src/components/Header.astro +125 -25
  26. package/eventcatalog/src/middleware.ts +62 -0
  27. package/eventcatalog/src/pages/auth/error.astro +55 -0
  28. package/eventcatalog/src/pages/auth/login.astro +231 -0
  29. package/eventcatalog/src/pages/directory/[type]/_index.data.ts +63 -0
  30. package/eventcatalog/src/pages/directory/[type]/index.astro +6 -23
  31. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +62 -0
  32. package/eventcatalog/src/pages/discover/[type]/index.astro +7 -24
  33. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +62 -0
  34. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +5 -37
  35. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/_[filename].data.ts +98 -0
  36. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +68 -0
  37. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +5 -25
  38. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +6 -25
  39. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +6 -35
  40. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/_[filename].data.ts +99 -0
  41. package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +1 -0
  42. package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +40 -0
  43. package/eventcatalog/src/pages/docs/[type]/[id]/{language.astro → language/index.astro} +6 -20
  44. package/eventcatalog/src/pages/docs/custom/[...path]/_index.data.ts +49 -0
  45. package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +5 -11
  46. package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +46 -0
  47. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +6 -10
  48. package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +46 -0
  49. package/eventcatalog/src/pages/docs/users/[id]/index.astro +5 -9
  50. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +99 -0
  51. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +5 -29
  52. package/eventcatalog/src/utils/feature.ts +10 -0
  53. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +12 -1
  54. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +11 -1
  55. package/eventcatalog/src/utils/page-loaders/hybrid-page.ts +68 -0
  56. package/eventcatalog/tsconfig.json +2 -1
  57. package/package.json +3 -1
  58. package/dist/{chunk-SLEMYHTU.js → chunk-SFA7F3CQ.js} +3 -3
@@ -0,0 +1,142 @@
1
+ import { defineConfig } from 'auth-astro';
2
+ import { join } from 'node:path';
3
+ import GitHub from '@auth/core/providers/github';
4
+ import Okta from '@auth/core/providers/okta';
5
+ import type { Account, Profile, User, Session } from '@auth/core/types';
6
+ import { isAuthEnabled, isSSR } from '@utils/feature';
7
+ import Google from '@auth/core/providers/google';
8
+
9
+ // Need to try and read the eventcatalog.auth.js file and get the auth providers from there
10
+ const catalogDirectory = process.env.PROJECT_DIR || process.cwd();
11
+
12
+ const getAuthProviders = async () => {
13
+ try {
14
+ const config = await import(/* @vite-ignore */ join(catalogDirectory, 'eventcatalog.auth.js'));
15
+ const authConfig = config.default;
16
+
17
+ const providers = [];
18
+
19
+ // GitHub provider
20
+ if (authConfig.providers?.github) {
21
+ const githubConfig = authConfig.providers.github;
22
+ providers.push(
23
+ GitHub({
24
+ clientId: githubConfig.clientId,
25
+ clientSecret: githubConfig.clientSecret,
26
+ })
27
+ );
28
+ console.log('✅ GitHub provider configured');
29
+ }
30
+
31
+ // Google provider
32
+ if (authConfig.providers?.google) {
33
+ const googleConfig = authConfig.providers.google;
34
+ providers.push(
35
+ Google({
36
+ clientId: googleConfig.clientId,
37
+ clientSecret: googleConfig.clientSecret,
38
+ })
39
+ );
40
+ console.log('✅ Google provider configured');
41
+ }
42
+
43
+ // Okta provider
44
+ if (authConfig.providers?.okta) {
45
+ const oktaConfig = authConfig.providers.okta;
46
+ providers.push(
47
+ Okta({
48
+ clientId: oktaConfig.clientId,
49
+ clientSecret: oktaConfig.clientSecret,
50
+ issuer: oktaConfig.issuer,
51
+ })
52
+ );
53
+ console.log('✅ Okta provider configured');
54
+ }
55
+
56
+ if (providers.length === 0) {
57
+ console.warn('⚠️ No auth providers configured');
58
+ }
59
+
60
+ return providers;
61
+ } catch (error) {
62
+ console.log('No eventcatalog.auth.js found or error loading config:', (error as Error).message);
63
+ return [];
64
+ }
65
+ };
66
+
67
+ const getAuthConfig = async () => {
68
+ // If auth is disabled or we are not in SSR, return an empty config
69
+ if (!isAuthEnabled() || !isSSR()) {
70
+ return {
71
+ providers: [],
72
+ };
73
+ }
74
+ try {
75
+ const config = await import(/* @vite-ignore */ join(catalogDirectory, 'eventcatalog.auth.js'));
76
+ const authConfig = config.default;
77
+
78
+ // If custom auth config is specified (Enterprise feature)
79
+ if (authConfig?.customAuthConfig) {
80
+ console.log('🚀 Loading custom auth configuration:', authConfig.customAuthConfig);
81
+ try {
82
+ const customConfig = await import(/* @vite-ignore */ join(catalogDirectory, authConfig.customAuthConfig));
83
+ return customConfig.default;
84
+ } catch (error) {
85
+ console.error('❌ Failed to load custom auth config:', error);
86
+ // Fall back to managed config
87
+ }
88
+ }
89
+
90
+ // Return managed auth config
91
+ const providers = await getAuthProviders();
92
+
93
+ return {
94
+ providers,
95
+ callbacks: {
96
+ async signIn({ user, account, profile }: { user: User; account: Account | null; profile?: Profile }) {
97
+ // Just allow everyone who can authenticate with the provider
98
+ return true;
99
+ },
100
+ async session({ session, token }: { session: Session; token: any }) {
101
+ // Add provider info to session
102
+ if (token?.provider) {
103
+ (session.user as any).provider = token.provider;
104
+ }
105
+ if (token?.login) {
106
+ (session.user as any).username = token.login;
107
+ }
108
+ return session;
109
+ },
110
+ async jwt({ token, account, profile }: { token: any; account: Account | null; profile?: Profile }) {
111
+ // Persist provider info in JWT
112
+ if (account && profile) {
113
+ token.provider = account.provider;
114
+ token.login = (profile as any).login || (profile as any).preferred_username;
115
+ }
116
+ return token;
117
+ },
118
+ },
119
+ pages: {
120
+ signIn: '/auth/login',
121
+ error: '/auth/error',
122
+ },
123
+ session: {
124
+ strategy: 'jwt' as const,
125
+ maxAge: authConfig?.session?.maxAge || 30 * 24 * 60 * 60, // 30 days default
126
+ },
127
+ debug: authConfig?.debug || false,
128
+ };
129
+ } catch (error) {
130
+ console.log(
131
+ 'No auth config found, auth disabled. If you want to use auth, create a eventcatalog.auth.js file in your project directory.'
132
+ );
133
+ return {
134
+ providers: [],
135
+ pages: {
136
+ signIn: '/auth/disabled',
137
+ },
138
+ };
139
+ }
140
+ };
141
+
142
+ export default defineConfig(await getAuthConfig());
@@ -3,6 +3,13 @@ import catalog from '@utils/eventcatalog-config/catalog';
3
3
  import Search from '@components/Search.astro';
4
4
  import { buildUrl } from '@utils/url-builder';
5
5
  import { showEventCatalogBranding, showCustomBranding } from '@utils/feature';
6
+ import { getSession } from 'auth-astro/server';
7
+ import { isAuthEnabled, isSSR } from '@utils/feature';
8
+
9
+ let session = null;
10
+ if (isAuthEnabled()) {
11
+ session = await getSession(Astro.request);
12
+ }
6
13
 
7
14
  const logo = {
8
15
  src: ('/' + (catalog?.logo?.src || 'logo.png')).replace(/^\/+/, '/'),
@@ -27,35 +34,94 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
27
34
  </div>
28
35
 
29
36
  <div class="hidden lg:block flex-grow w-6/12 px-10">
30
- <Search />
37
+ <!-- Page find only works on static builds, disable for SSR builds for now -->
38
+ {!isSSR() && <Search />}
31
39
  </div>
32
40
 
33
41
  <div class="hidden md:block w-3/12">
34
42
  {
35
- showEventCatalogBranding() && (
36
- <ul class="flex space-x-8 justify-end pr-2">
37
- <li>
38
- <a href="https://discord.com/invite/3rjaZMmrAm">
39
- <img src={buildUrl('/icons/discord.svg', true)} class="h-7 w-7" />
40
- </a>
41
- </li>
42
- <li>
43
- <a href="https://github.com/event-catalog/eventcatalog">
44
- <img src={buildUrl('/icons/github.svg', true)} class="h-7 w-7" />
45
- </a>
46
- </li>
47
- </ul>
48
- )
49
- }
50
- {
51
- showCustomBranding() && !showEventCatalogBranding() && (
52
- <ul class="flex space-x-8 justify-end pr-2">
53
- <li>
54
- <a href={repositoryUrl} class="text-gray-500 hover:text-gray-600 focus:outline-none focus:text-gray-600">
55
- <img src={buildUrl('/icons/github.svg', true)} class="h-7 w-7" />
56
- </a>
57
- </li>
58
- </ul>
43
+ session ? (
44
+ <div class="flex justify-end pr-2">
45
+ <div class="relative">
46
+ <button
47
+ id="profile-menu-button"
48
+ type="button"
49
+ class="flex items-center focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 rounded-full"
50
+ aria-expanded="false"
51
+ aria-haspopup="true"
52
+ >
53
+ {session.user?.image ? (
54
+ <img
55
+ src={session.user.image}
56
+ alt={session.user?.name || 'User'}
57
+ class="h-8 w-8 rounded-full border-2 border-gray-200 hover:border-gray-300 transition-colors"
58
+ />
59
+ ) : (
60
+ <div class="h-8 w-8 rounded-full border-2 border-gray-200 hover:border-gray-300 transition-colors bg-gray-100 flex items-center justify-center text-sm font-medium text-gray-600">
61
+ {session.user?.name
62
+ ? session.user.name
63
+ .split(' ')
64
+ .map((n) => n[0])
65
+ .join('')
66
+ .substring(0, 2)
67
+ .toUpperCase()
68
+ : 'U'}
69
+ </div>
70
+ )}
71
+ </button>
72
+ <div
73
+ id="profile-dropdown"
74
+ class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50 border border-gray-100"
75
+ >
76
+ <div class="px-4 py-2 text-sm text-gray-700 border-b border-gray-100">
77
+ <div class="font-medium">{session.user?.name || 'User'}</div>
78
+ {session.user?.email && <div class="text-gray-500">{session.user.email}</div>}
79
+ </div>
80
+ <button
81
+ id="signout-btn"
82
+ class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
83
+ >
84
+ Sign out
85
+ </button>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ ) : (
90
+ <>
91
+ <div class="flex items-center space-x-4 justify-end pr-2">
92
+ {isAuthEnabled() && (
93
+ <button
94
+ id="okta-signin-btn"
95
+ class="bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium px-4 py-2 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
96
+ >
97
+ Sign In
98
+ </button>
99
+ )}
100
+ {showEventCatalogBranding() && (
101
+ <ul class="flex space-x-8">
102
+ <li>
103
+ <a href="https://discord.com/invite/3rjaZMmrAm">
104
+ <img src={buildUrl('/icons/discord.svg', true)} class="h-7 w-7" />
105
+ </a>
106
+ </li>
107
+ <li>
108
+ <a href="https://github.com/event-catalog/eventcatalog">
109
+ <img src={buildUrl('/icons/github.svg', true)} class="h-7 w-7" />
110
+ </a>
111
+ </li>
112
+ </ul>
113
+ )}
114
+ {showCustomBranding() && !showEventCatalogBranding() && (
115
+ <ul class="flex space-x-8">
116
+ <li>
117
+ <a href={repositoryUrl} class="text-gray-500 hover:text-gray-600 focus:outline-none focus:text-gray-600">
118
+ <img src={buildUrl('/icons/github.svg', true)} class="h-7 w-7" />
119
+ </a>
120
+ </li>
121
+ </ul>
122
+ )}
123
+ </div>
124
+ </>
59
125
  )
60
126
  }
61
127
  </div>
@@ -100,12 +166,46 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
100
166
  <script>
101
167
  const menuToggle = document.getElementById('menu-toggle');
102
168
  const mobileMenu = document.getElementById('mobile-menu');
169
+ import { signOut } from 'auth-astro/client';
103
170
 
104
171
  if (menuToggle && mobileMenu) {
105
172
  menuToggle.addEventListener('click', () => {
106
173
  mobileMenu.classList.toggle('hidden');
107
174
  });
108
175
  }
176
+
177
+ // Profile dropdown functionality
178
+ const profileButton = document.getElementById('profile-menu-button');
179
+ const profileDropdown = document.getElementById('profile-dropdown');
180
+
181
+ if (profileButton && profileDropdown) {
182
+ profileButton.addEventListener('click', (e) => {
183
+ e.stopPropagation();
184
+ profileDropdown.classList.toggle('hidden');
185
+ profileButton.setAttribute('aria-expanded', !profileDropdown.classList.contains('hidden') ? 'true' : 'false');
186
+ });
187
+
188
+ // Close dropdown when clicking outside
189
+ document.addEventListener('click', (e) => {
190
+ if (!profileButton.contains(e.target as Node) && !profileDropdown.contains(e.target as Node)) {
191
+ profileDropdown.classList.add('hidden');
192
+ profileButton.setAttribute('aria-expanded', 'false');
193
+ }
194
+ });
195
+
196
+ // Close dropdown on escape key
197
+ document.addEventListener('keydown', (e) => {
198
+ if (e.key === 'Escape' && !profileDropdown.classList.contains('hidden')) {
199
+ profileDropdown.classList.add('hidden');
200
+ profileButton.setAttribute('aria-expanded', 'false');
201
+ profileButton.focus();
202
+ }
203
+ });
204
+
205
+ document.getElementById('signout-btn')?.addEventListener('click', async () => {
206
+ await signOut();
207
+ });
208
+ }
109
209
  </script>
110
210
 
111
211
  <style>
@@ -0,0 +1,62 @@
1
+ // src/middleware.ts
2
+ import type { MiddlewareHandler } from 'astro';
3
+ import { getSession } from 'auth-astro/server';
4
+ import { isAuthEnabled } from '@utils/feature';
5
+
6
+ export const onRequest: MiddlewareHandler = async (context, next) => {
7
+ const { request, redirect, locals } = context;
8
+ const url = new URL(request.url);
9
+ const pathname = url.pathname;
10
+
11
+ // If auth is disabled and we are on an auth route, redirect to home
12
+ if (!isAuthEnabled() && pathname.includes('/auth')) {
13
+ return redirect('/');
14
+ }
15
+
16
+ // Auth is disabled, skip auth check
17
+ if (!isAuthEnabled()) {
18
+ return next();
19
+ }
20
+
21
+ // Skip system/browser requests
22
+ const systemRoutes = ['/.well-known/', '/favicon.ico', '/robots.txt', '/sitemap.xml', '/_astro/', '/__astro'];
23
+
24
+ // Skip auth check for these routes
25
+ const publicRoutes = ['/auth/login', '/auth/signout', '/auth/error', '/api/auth'];
26
+
27
+ // Skip static files, system routes, and browser requests
28
+ if (
29
+ pathname.startsWith('/_') ||
30
+ systemRoutes.some((route) => pathname.startsWith(route)) ||
31
+ pathname.startsWith('/.well-known/')
32
+ ) {
33
+ return next();
34
+ }
35
+
36
+ // Skip public routes
37
+ if (publicRoutes.some((route) => pathname.startsWith(route))) {
38
+ return next();
39
+ }
40
+
41
+ try {
42
+ // Check if user is logged in
43
+ const session = await getSession(request);
44
+
45
+ if (!session) {
46
+ const callbackUrl = encodeURIComponent(pathname + url.search);
47
+ return redirect(`/auth/login?callbackUrl=${callbackUrl}`);
48
+ }
49
+
50
+ // Add session to locals for pages to use
51
+ // @ts-ignore
52
+ locals.session = session;
53
+ // @ts-ignore
54
+ locals.user = session.user;
55
+ } catch (error) {
56
+ console.error('Session error:', error);
57
+ const callbackUrl = encodeURIComponent(pathname + url.search);
58
+ return redirect(`/auth/login?callbackUrl=${callbackUrl}`);
59
+ }
60
+
61
+ return next();
62
+ };
@@ -0,0 +1,55 @@
1
+ ---
2
+ // src/pages/auth/error.astro
3
+
4
+ const { searchParams } = new URL(Astro.request.url);
5
+ const error = searchParams.get('error');
6
+
7
+ const errorMessages = {
8
+ Configuration: 'There is a problem with the server configuration.',
9
+ AccessDenied: 'You do not have permission to access this resource.',
10
+ Verification: 'The verification token has expired or has already been used.',
11
+ Default: 'An error occurred during authentication.',
12
+ };
13
+
14
+ // @ts-ignore
15
+ const errorMessage = errorMessages[error] || errorMessages.Default;
16
+ ---
17
+
18
+ <main title="Authentication Error - EventCatalog">
19
+ <div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
20
+ <div class="max-w-md w-full space-y-8 text-center">
21
+ <div>
22
+ <svg class="mx-auto h-12 w-12 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
23
+ <path
24
+ stroke-linecap="round"
25
+ stroke-linejoin="round"
26
+ stroke-width="2"
27
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
28
+ ></path>
29
+ </svg>
30
+ <h2 class="mt-6 text-3xl font-bold text-gray-900">Authentication Error</h2>
31
+ <p class="mt-2 text-sm text-gray-600">
32
+ {errorMessage}
33
+ </p>
34
+ </div>
35
+
36
+ <div class="space-y-4">
37
+ <a
38
+ href="/auth/login"
39
+ class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
40
+ >
41
+ Try Again
42
+ </a>
43
+
44
+ <a
45
+ href="/"
46
+ class="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
47
+ >
48
+ Back to Home
49
+ </a>
50
+ </div>
51
+
52
+ {error && <div class="mt-8 text-xs text-gray-400">Error code: {error}</div>}
53
+ </div>
54
+ </div>
55
+ </main>
@@ -0,0 +1,231 @@
1
+ ---
2
+ import config from '@config';
3
+ const { title, logo } = config;
4
+ import { getSession } from 'auth-astro/server';
5
+ import { join } from 'node:path';
6
+ import { isAuthEnabled, isSSR } from '@utils/feature';
7
+
8
+ const session = await getSession(Astro.request);
9
+ const catalogDirectory = process.env.PROJECT_DIR || process.cwd();
10
+
11
+ let hasAuthConfigurationFile = false;
12
+ let providers: string[] = [];
13
+
14
+ try {
15
+ const authConfig = await import(/* @vite-ignore */ join(catalogDirectory, 'eventcatalog.auth.js'));
16
+ providers = Object.keys(authConfig.default.providers);
17
+ hasAuthConfigurationFile = true;
18
+ } catch (error) {
19
+ hasAuthConfigurationFile = false;
20
+ }
21
+
22
+ // Check if we should show login (auth file exists, SSR enabled, auth enabled, and has providers)
23
+ const shouldShowLogin = hasAuthConfigurationFile && isSSR() && isAuthEnabled() && providers.length > 0;
24
+
25
+ // Check if configuration exists but no providers are set up
26
+ const hasConfigButNoProviders = hasAuthConfigurationFile && isSSR() && isAuthEnabled() && providers.length === 0;
27
+
28
+ if (session) {
29
+ return Astro.redirect('/');
30
+ }
31
+
32
+ // Provider configurations
33
+ const providerConfig = {
34
+ github: {
35
+ name: 'GitHub',
36
+ icon: `<svg class="mr-2 h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
37
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path>
38
+ </svg>`,
39
+ },
40
+ google: {
41
+ name: 'Google',
42
+ icon: `<svg class="mr-2 h-5 w-5" viewBox="0 0 24 24">
43
+ <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
44
+ <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
45
+ <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
46
+ <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
47
+ </svg>`,
48
+ },
49
+ okta: {
50
+ name: 'Okta',
51
+ icon: `<svg class="mr-2 h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
52
+ <path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm0 18c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8zm-1-13h2v6h-2V7zm0 8h2v2h-2v-2z"></path>
53
+ </svg>`,
54
+ },
55
+ };
56
+ ---
57
+
58
+ <!doctype html>
59
+ <html lang="en">
60
+ <head>
61
+ <meta charset="UTF-8" />
62
+ <meta name="viewport" content="width=device-width" />
63
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
64
+ <meta name="generator" content={Astro.generator} />
65
+ <title>Sign In | {title}</title>
66
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
67
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
68
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
69
+ <style>
70
+ body {
71
+ font-family: 'Inter', sans-serif;
72
+ }
73
+ </style>
74
+ </head>
75
+ <body class="bg-gray-50 min-h-screen">
76
+ <!-- Header -->
77
+ <nav class="fixed top-0 left-0 right-0 h-16 bg-white border-b border-gray-100 py-3 z-10">
78
+ <div class="px-4 sm:px-4 lg:px-4">
79
+ <div class="flex justify-between items-center h-10">
80
+ <!-- Logo -->
81
+ <div class="flex-shrink-0 flex items-center">
82
+ {
83
+ logo && (
84
+ <a href="/" class="flex space-x-2 items-center">
85
+ <img alt={logo.alt} src={logo.src} class="w-8 h-8" />
86
+ <span class="hidden sm:inline-block text-lg font-bold">{title}</span>
87
+ </a>
88
+ )
89
+ }
90
+ </div>
91
+
92
+ <!-- GitHub Link -->
93
+ <div class="flex items-center">
94
+ <a
95
+ href="https://github.com/event-catalog/eventcatalog"
96
+ target="_blank"
97
+ class="text-gray-500 hover:text-gray-600 focus:outline-none focus:text-gray-600"
98
+ >
99
+ <svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
100
+ <path
101
+ d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
102
+ ></path>
103
+ </svg>
104
+ </a>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </nav>
109
+
110
+ <!-- Content with top padding to account for fixed header -->
111
+ <div class="flex min-h-screen flex-col items-center justify-center py-12 px-4 sm:px-6 lg:px-8 pt-24 bg-gray-50">
112
+ {
113
+ shouldShowLogin ? (
114
+ <div class="w-full max-w-md space-y-8">
115
+ <div class="text-center">
116
+ <h2 class="text-3xl font-bold tracking-tight text-gray-900">Welcome to {title} EventCatalog</h2>
117
+ <p class="mt-2 text-gray-600">Sign in to your account</p>
118
+ </div>
119
+
120
+ <div class="mt-8 space-y-4">
121
+ {providers.map((provider) => {
122
+ const config = providerConfig[provider as keyof typeof providerConfig];
123
+ if (!config) return null;
124
+
125
+ return (
126
+ <button
127
+ data-provider={provider}
128
+ class="provider-login-btn flex w-full items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-3 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2"
129
+ >
130
+ <Fragment set:html={config.icon} />
131
+ Sign in with {config.name}
132
+ </button>
133
+ );
134
+ })}
135
+ </div>
136
+ </div>
137
+ ) : hasConfigButNoProviders ? (
138
+ <div class="w-full max-w-2xl space-y-8">
139
+ <div class="text-center">
140
+ <h2 class="text-3xl font-bold tracking-tight text-gray-900">No Authentication Providers Configured</h2>
141
+ <p class="mt-4 text-lg text-gray-600">
142
+ Authentication is enabled but no providers are configured in your auth configuration file.
143
+ </p>
144
+ </div>
145
+
146
+ <div class="bg-white shadow-sm rounded-lg p-6 space-y-4">
147
+ <h3 class="text-lg font-semibold text-gray-900">To add authentication providers:</h3>
148
+ <ol class="list-decimal list-inside space-y-2 text-gray-700">
149
+ <li>
150
+ Update your <code class="bg-gray-100 px-2 py-1 rounded text-sm">eventcatalog.auth.js</code> file to include at
151
+ least one provider (GitHub, Google, Okta, etc.)
152
+ </li>
153
+ <li>Configure the provider with the necessary credentials and settings</li>
154
+ <li>Restart your EventCatalog server to apply the changes</li>
155
+ </ol>
156
+ </div>
157
+
158
+ <div class="text-center">
159
+ <a
160
+ href="#"
161
+ class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
162
+ >
163
+ Read Authentication Documentation
164
+ </a>
165
+ </div>
166
+ </div>
167
+ ) : (
168
+ <div class="w-full max-w-2xl space-y-8">
169
+ <div class="text-center">
170
+ <h2 class="text-3xl font-bold tracking-tight text-gray-900">Authentication Not Configured</h2>
171
+ <p class="mt-4 text-lg text-gray-600">
172
+ {!hasAuthConfigurationFile
173
+ ? 'No authentication configuration file found.'
174
+ : 'Authentication is not properly enabled.'}
175
+ </p>
176
+ </div>
177
+
178
+ <div class="bg-white shadow-sm rounded-lg p-6 space-y-4">
179
+ <h3 class="text-lg font-semibold text-gray-900">To enable authentication:</h3>
180
+ <ol class="list-decimal list-inside space-y-2 text-gray-700">
181
+ {!hasAuthConfigurationFile && (
182
+ <li>
183
+ Create an <code class="bg-gray-100 px-2 py-1 rounded text-sm">eventcatalog.auth.js</code> configuration file
184
+ in your project root
185
+ </li>
186
+ )}
187
+ {!isSSR() && (
188
+ <li>
189
+ Enable SSR (Server-Side Rendering) in your{' '}
190
+ <code class="bg-gray-100 px-2 py-1 rounded text-sm">eventcatalog.config.js</code> file by setting{' '}
191
+ <code class="bg-gray-100 px-2 py-1 rounded text-sm">output: 'server'</code>
192
+ </li>
193
+ )}
194
+ {!isAuthEnabled() && (
195
+ <li>
196
+ Enable authentication in your{' '}
197
+ <code class="bg-gray-100 px-2 py-1 rounded text-sm">eventcatalog.config.js</code> file by setting{' '}
198
+ <code class="bg-gray-100 px-2 py-1 rounded text-sm">auth: {`{ enabled: true }`}</code>
199
+ </li>
200
+ )}
201
+ </ol>
202
+ </div>
203
+
204
+ <div class="text-center">
205
+ <a
206
+ href="#"
207
+ class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
208
+ >
209
+ Read Authentication Documentation
210
+ </a>
211
+ </div>
212
+ </div>
213
+ )
214
+ }
215
+ </div>
216
+ </body>
217
+ </html>
218
+
219
+ <script>
220
+ import { signIn } from 'auth-astro/client';
221
+
222
+ // Add event listeners to all provider login buttons
223
+ const providerButtons = document.querySelectorAll('.provider-login-btn');
224
+
225
+ providerButtons.forEach((button) => {
226
+ const provider = button.getAttribute('data-provider');
227
+ if (provider) {
228
+ button.addEventListener('click', () => signIn(provider));
229
+ }
230
+ });
231
+ </script>