@knowcode/doc-builder 1.7.3 → 1.7.5
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/.claude/settings.local.json +6 -1
- package/CHANGELOG.md +24 -0
- package/README.md +23 -6
- package/RELEASE-NOTES-1.7.5.md +64 -0
- package/cli.js +214 -7
- package/grant-access.sql +15 -0
- package/html/README.html +14 -4
- package/html/auth.js +97 -0
- package/html/documentation-index.html +14 -4
- package/html/guides/authentication-default-change.html +302 -0
- package/html/guides/authentication-guide.html +32 -14
- package/html/guides/cache-control-anti-pattern.html +361 -0
- package/html/guides/claude-workflow-guide.html +14 -4
- package/html/guides/documentation-standards.html +14 -4
- package/html/guides/next-steps-walkthrough.html +638 -0
- package/html/guides/phosphor-icons-guide.html +14 -4
- package/html/guides/public-site-deployment.html +363 -0
- package/html/guides/search-engine-verification-guide.html +14 -4
- package/html/guides/seo-guide.html +14 -4
- package/html/guides/seo-optimization-guide.html +14 -4
- package/html/guides/supabase-auth-implementation-plan.html +543 -0
- package/html/guides/supabase-auth-integration-plan.html +671 -0
- package/html/guides/supabase-auth-setup-guide.html +498 -0
- package/html/guides/troubleshooting-guide.html +14 -4
- package/html/guides/vercel-deployment-auth-setup.html +337 -0
- package/html/guides/windows-setup-guide.html +14 -4
- package/html/index.html +14 -4
- package/html/launch/README.html +14 -4
- package/html/launch/bubble-plugin-specification.html +14 -4
- package/html/launch/go-to-market-strategy.html +14 -4
- package/html/launch/launch-announcements.html +14 -4
- package/html/login.html +102 -0
- package/html/logout.html +18 -0
- package/html/sitemap.xml +69 -21
- package/html/vercel-cli-setup-guide.html +14 -4
- package/html/vercel-first-time-setup-guide.html +14 -4
- package/lib/config.js +11 -26
- package/lib/core-builder.js +51 -87
- package/lib/supabase-auth.js +295 -0
- package/package.json +2 -1
- package/public-config.js +22 -0
- package/public-html/404.html +115 -0
- package/public-html/README.html +149 -0
- package/public-html/css/notion-style.css +2036 -0
- package/public-html/index.html +149 -0
- package/public-html/js/auth.js +67 -0
- package/public-html/js/main.js +1485 -0
- package/quick-test-commands.md +40 -0
- package/recordings/Screenshot 2025-07-24 at 18.22.01.png +0 -0
- package/setup-database.sql +41 -0
- package/test-auth-config.js +17 -0
- package/test-docs/README.md +39 -0
- package/test-html/404.html +115 -0
- package/test-html/README.html +172 -0
- package/test-html/auth.js +97 -0
- package/test-html/css/notion-style.css +2036 -0
- package/test-html/index.html +172 -0
- package/test-html/js/auth.js +97 -0
- package/test-html/js/main.js +1485 -0
- package/test-html/login.html +102 -0
- package/test-html/logout.html +18 -0
- package/update-domain.sql +9 -0
package/lib/config.js
CHANGED
|
@@ -19,7 +19,7 @@ const defaultConfig = {
|
|
|
19
19
|
|
|
20
20
|
// Features
|
|
21
21
|
features: {
|
|
22
|
-
authentication: false,
|
|
22
|
+
authentication: false, // false (no auth) or 'supabase' (secure auth)
|
|
23
23
|
changelog: true,
|
|
24
24
|
mermaid: true,
|
|
25
25
|
tooltips: true,
|
|
@@ -33,10 +33,11 @@ const defaultConfig = {
|
|
|
33
33
|
menuDefaultOpen: true // Menu/sidebar open by default
|
|
34
34
|
},
|
|
35
35
|
|
|
36
|
-
// Authentication (
|
|
36
|
+
// Authentication - Supabase only (basic auth removed for security)
|
|
37
37
|
auth: {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
supabaseUrl: '',
|
|
39
|
+
supabaseAnonKey: '',
|
|
40
|
+
siteId: ''
|
|
40
41
|
},
|
|
41
42
|
|
|
42
43
|
// Changelog settings
|
|
@@ -90,7 +91,7 @@ const notionInspiredPreset = {
|
|
|
90
91
|
siteDescription: 'Transforming complex sales through intelligent automation',
|
|
91
92
|
|
|
92
93
|
features: {
|
|
93
|
-
authentication:
|
|
94
|
+
authentication: false, // Default to no auth - can be enabled with 'supabase'
|
|
94
95
|
changelog: true,
|
|
95
96
|
mermaid: true,
|
|
96
97
|
tooltips: true,
|
|
@@ -105,8 +106,9 @@ const notionInspiredPreset = {
|
|
|
105
106
|
},
|
|
106
107
|
|
|
107
108
|
auth: {
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
supabaseUrl: process.env.SUPABASE_URL || '',
|
|
110
|
+
supabaseAnonKey: process.env.SUPABASE_ANON_KEY || '',
|
|
111
|
+
siteId: process.env.DOC_SITE_ID || ''
|
|
110
112
|
},
|
|
111
113
|
|
|
112
114
|
changelog: {
|
|
@@ -307,32 +309,15 @@ async function createDefaultConfig() {
|
|
|
307
309
|
{
|
|
308
310
|
type: 'confirm',
|
|
309
311
|
name: 'authentication',
|
|
310
|
-
message: 'Enable authentication?',
|
|
312
|
+
message: 'Enable Supabase authentication?',
|
|
311
313
|
initial: false
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
type: prev => prev ? 'text' : null,
|
|
315
|
-
name: 'authUsername',
|
|
316
|
-
message: 'Authentication username:',
|
|
317
|
-
initial: 'admin'
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
type: prev => prev ? 'password' : null,
|
|
321
|
-
name: 'authPassword',
|
|
322
|
-
message: 'Authentication password:',
|
|
323
|
-
initial: 'password'
|
|
324
314
|
}
|
|
325
315
|
]);
|
|
326
316
|
|
|
327
317
|
const config = { ...defaultConfig };
|
|
328
318
|
config.siteName = answers.siteName;
|
|
329
319
|
config.siteDescription = answers.siteDescription;
|
|
330
|
-
config.features.authentication = answers.authentication;
|
|
331
|
-
|
|
332
|
-
if (answers.authentication) {
|
|
333
|
-
config.auth.username = answers.authUsername;
|
|
334
|
-
config.auth.password = answers.authPassword;
|
|
335
|
-
}
|
|
320
|
+
config.features.authentication = answers.authentication ? 'supabase' : false;
|
|
336
321
|
|
|
337
322
|
return config;
|
|
338
323
|
}
|
package/lib/core-builder.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const marked = require('marked');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const matter = require('gray-matter');
|
|
6
|
+
const SupabaseAuth = require('./supabase-auth');
|
|
6
7
|
const {
|
|
7
8
|
generateMetaTags,
|
|
8
9
|
generateJSONLD,
|
|
@@ -344,6 +345,26 @@ ${seoTags}
|
|
|
344
345
|
<!-- Styles -->
|
|
345
346
|
<link rel="stylesheet" href="/css/notion-style.css">
|
|
346
347
|
|
|
348
|
+
${config.features?.authentication === 'supabase' ? `
|
|
349
|
+
<!-- Hide content until auth check -->
|
|
350
|
+
<style>
|
|
351
|
+
body {
|
|
352
|
+
visibility: hidden;
|
|
353
|
+
opacity: 0;
|
|
354
|
+
transition: opacity 0.3s ease;
|
|
355
|
+
}
|
|
356
|
+
body.authenticated {
|
|
357
|
+
visibility: visible;
|
|
358
|
+
opacity: 1;
|
|
359
|
+
}
|
|
360
|
+
/* Show login/logout pages immediately */
|
|
361
|
+
body.auth-page {
|
|
362
|
+
visibility: visible;
|
|
363
|
+
opacity: 1;
|
|
364
|
+
}
|
|
365
|
+
</style>
|
|
366
|
+
` : ''}
|
|
367
|
+
|
|
347
368
|
<!-- Favicon -->
|
|
348
369
|
${generateFaviconTag(config.favicon || '✨')}
|
|
349
370
|
|
|
@@ -367,7 +388,7 @@ ${seoTags}
|
|
|
367
388
|
})} UTC</span>
|
|
368
389
|
</div>
|
|
369
390
|
|
|
370
|
-
${config.features?.authentication ? `
|
|
391
|
+
${config.features?.authentication === 'supabase' ? `
|
|
371
392
|
<a href="${relativePath}logout.html" class="logout-btn" title="Logout">
|
|
372
393
|
<i class="fas fa-sign-out-alt"></i>
|
|
373
394
|
</a>
|
|
@@ -435,7 +456,8 @@ ${seoTags}
|
|
|
435
456
|
};
|
|
436
457
|
</script>
|
|
437
458
|
<script src="/js/main.js"></script>
|
|
438
|
-
${config.features?.authentication ? `<script src="/js
|
|
459
|
+
${config.features?.authentication === 'supabase' ? `<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
|
460
|
+
<script src="/js/auth.js"></script>` : ''}
|
|
439
461
|
</body>
|
|
440
462
|
</html>`;
|
|
441
463
|
}
|
|
@@ -778,21 +800,12 @@ async function buildDocumentation(config) {
|
|
|
778
800
|
if (fs.existsSync(jsSource)) {
|
|
779
801
|
await fs.copy(jsSource, path.join(outputDir, 'js'), { overwrite: true });
|
|
780
802
|
|
|
781
|
-
//
|
|
782
|
-
if (config.features?.authentication) {
|
|
783
|
-
|
|
784
|
-
const authDest = path.join(outputDir, 'auth.js');
|
|
785
|
-
if (fs.existsSync(authSource)) {
|
|
786
|
-
await fs.copy(authSource, authDest, { overwrite: true });
|
|
787
|
-
}
|
|
803
|
+
// Generate Supabase auth script if authentication is enabled
|
|
804
|
+
if (config.features?.authentication === 'supabase') {
|
|
805
|
+
await generateSupabaseAuthFiles(outputDir, config);
|
|
788
806
|
}
|
|
789
807
|
}
|
|
790
808
|
|
|
791
|
-
// Create auth pages if needed
|
|
792
|
-
if (config.features?.authentication) {
|
|
793
|
-
await createAuthPages(outputDir, config);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
809
|
// Copy 404.html for handling .md redirects
|
|
797
810
|
const notFoundSource = path.join(assetsDir, '404.html');
|
|
798
811
|
if (fs.existsSync(notFoundSource)) {
|
|
@@ -928,7 +941,7 @@ async function buildDocumentation(config) {
|
|
|
928
941
|
// Generate robots.txt if enabled
|
|
929
942
|
if (config.seo.generateRobotsTxt) {
|
|
930
943
|
await generateRobotsTxt(config.seo.siteUrl, outputDir, {
|
|
931
|
-
hasAuthentication: config.features?.authentication
|
|
944
|
+
hasAuthentication: config.features?.authentication === 'supabase'
|
|
932
945
|
});
|
|
933
946
|
}
|
|
934
947
|
}
|
|
@@ -1047,82 +1060,33 @@ For help with @knowcode/doc-builder:
|
|
|
1047
1060
|
}
|
|
1048
1061
|
}
|
|
1049
1062
|
|
|
1050
|
-
//
|
|
1051
|
-
async function
|
|
1052
|
-
//
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1058
|
-
<title>Login - ${config.siteName}</title>
|
|
1059
|
-
<link rel="stylesheet" href="css/notion-style.css">
|
|
1060
|
-
</head>
|
|
1061
|
-
<body>
|
|
1062
|
-
<div class="auth-container">
|
|
1063
|
-
<div class="auth-box">
|
|
1064
|
-
<h1>Login to ${config.siteName}</h1>
|
|
1065
|
-
<form id="login-form">
|
|
1066
|
-
<div class="form-group">
|
|
1067
|
-
<label for="username">Username</label>
|
|
1068
|
-
<input type="text" id="username" name="username" required>
|
|
1069
|
-
</div>
|
|
1070
|
-
<div class="form-group">
|
|
1071
|
-
<label for="password">Password</label>
|
|
1072
|
-
<input type="password" id="password" name="password" required>
|
|
1073
|
-
</div>
|
|
1074
|
-
<button type="submit" class="auth-button">Login</button>
|
|
1075
|
-
</form>
|
|
1076
|
-
<div id="error-message" class="error-message"></div>
|
|
1077
|
-
</div>
|
|
1078
|
-
</div>
|
|
1079
|
-
<script>
|
|
1080
|
-
document.getElementById('login-form').addEventListener('submit', function(e) {
|
|
1081
|
-
e.preventDefault();
|
|
1082
|
-
const username = document.getElementById('username').value;
|
|
1083
|
-
const password = document.getElementById('password').value;
|
|
1084
|
-
|
|
1085
|
-
// Validate credentials
|
|
1086
|
-
if (username === '${config.auth.username}' && password === '${config.auth.password}') {
|
|
1087
|
-
// Set auth cookie
|
|
1088
|
-
const token = btoa(username + ':' + password);
|
|
1089
|
-
document.cookie = 'doc-auth=' + token + '; path=/';
|
|
1090
|
-
|
|
1091
|
-
// Redirect
|
|
1092
|
-
const params = new URLSearchParams(window.location.search);
|
|
1093
|
-
const redirect = params.get('redirect') || '/';
|
|
1094
|
-
window.location.href = redirect;
|
|
1095
|
-
} else {
|
|
1096
|
-
document.getElementById('error-message').textContent = 'Invalid username or password';
|
|
1097
|
-
}
|
|
1098
|
-
});
|
|
1099
|
-
</script>
|
|
1100
|
-
</body>
|
|
1101
|
-
</html>`;
|
|
1063
|
+
// Generate all Supabase authentication files
|
|
1064
|
+
async function generateSupabaseAuthFiles(outputDir, config) {
|
|
1065
|
+
// Validate Supabase configuration
|
|
1066
|
+
const validationErrors = SupabaseAuth.validateConfig(config);
|
|
1067
|
+
if (validationErrors.length > 0) {
|
|
1068
|
+
throw new Error(`Supabase authentication configuration errors:\n${validationErrors.join('\n')}`);
|
|
1069
|
+
}
|
|
1102
1070
|
|
|
1103
|
-
|
|
1071
|
+
// Create Supabase auth instance
|
|
1072
|
+
const supabaseAuth = new SupabaseAuth(config.auth);
|
|
1104
1073
|
|
|
1105
|
-
//
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
<title>Logged Out - ${config.siteName}</title>
|
|
1112
|
-
<link rel="stylesheet" href="css/notion-style.css">
|
|
1113
|
-
</head>
|
|
1114
|
-
<body>
|
|
1115
|
-
<div class="auth-container">
|
|
1116
|
-
<div class="auth-box">
|
|
1117
|
-
<h1>You have been logged out</h1>
|
|
1118
|
-
<p>Thank you for using ${config.siteName}.</p>
|
|
1119
|
-
<a href="login.html" class="auth-button">Login Again</a>
|
|
1120
|
-
</div>
|
|
1121
|
-
</div>
|
|
1122
|
-
</body>
|
|
1123
|
-
</html>`;
|
|
1074
|
+
// Generate auth script and save to js/auth.js
|
|
1075
|
+
const authScript = supabaseAuth.generateAuthScript();
|
|
1076
|
+
await fs.writeFile(path.join(outputDir, 'js', 'auth.js'), authScript);
|
|
1077
|
+
|
|
1078
|
+
// Also copy to root for backward compatibility
|
|
1079
|
+
await fs.writeFile(path.join(outputDir, 'auth.js'), authScript);
|
|
1124
1080
|
|
|
1081
|
+
// Generate and write login page
|
|
1082
|
+
const loginHTML = supabaseAuth.generateLoginPage(config);
|
|
1083
|
+
await fs.writeFile(path.join(outputDir, 'login.html'), loginHTML);
|
|
1084
|
+
|
|
1085
|
+
// Generate and write logout page
|
|
1086
|
+
const logoutHTML = supabaseAuth.generateLogoutPage(config);
|
|
1125
1087
|
await fs.writeFile(path.join(outputDir, 'logout.html'), logoutHTML);
|
|
1088
|
+
|
|
1089
|
+
console.log(chalk.green('✓ Generated Supabase authentication files'));
|
|
1126
1090
|
}
|
|
1127
1091
|
|
|
1128
1092
|
// Create default index page when no documentation exists
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const { createClient } = require('@supabase/supabase-js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Supabase Authentication Module for @knowcode/doc-builder
|
|
5
|
+
*
|
|
6
|
+
* This module provides secure authentication functionality using Supabase's
|
|
7
|
+
* built-in auth system. It replaces the insecure basic auth implementation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class SupabaseAuth {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
if (!config.supabaseUrl || !config.supabaseAnonKey) {
|
|
13
|
+
throw new Error('Supabase URL and anonymous key are required for authentication');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.supabase = createClient(config.supabaseUrl, config.supabaseAnonKey, {
|
|
18
|
+
auth: {
|
|
19
|
+
persistSession: true,
|
|
20
|
+
autoRefreshToken: true,
|
|
21
|
+
detectSessionInUrl: true
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
this.siteId = config.siteId;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generate client-side auth script for inclusion in HTML pages
|
|
30
|
+
*/
|
|
31
|
+
generateAuthScript() {
|
|
32
|
+
return `
|
|
33
|
+
/**
|
|
34
|
+
* Supabase Authentication for Documentation Site
|
|
35
|
+
* Generated by @knowcode/doc-builder
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
(function() {
|
|
39
|
+
'use strict';
|
|
40
|
+
|
|
41
|
+
// Skip auth check on login and logout pages
|
|
42
|
+
const currentPage = window.location.pathname;
|
|
43
|
+
if (currentPage === '/login.html' || currentPage === '/logout.html' ||
|
|
44
|
+
currentPage.includes('login') || currentPage.includes('logout')) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Initialize Supabase client
|
|
49
|
+
const { createClient } = supabase;
|
|
50
|
+
const supabaseClient = createClient('${this.config.supabaseUrl}', '${this.config.supabaseAnonKey}', {
|
|
51
|
+
auth: {
|
|
52
|
+
persistSession: true,
|
|
53
|
+
autoRefreshToken: true,
|
|
54
|
+
detectSessionInUrl: true
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Check authentication and site access
|
|
59
|
+
async function checkAuth() {
|
|
60
|
+
try {
|
|
61
|
+
// Get current user session
|
|
62
|
+
const { data: { user }, error: userError } = await supabaseClient.auth.getUser();
|
|
63
|
+
|
|
64
|
+
if (userError || !user) {
|
|
65
|
+
redirectToLogin();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check if user has access to this site
|
|
70
|
+
const { data: access, error: accessError } = await supabaseClient
|
|
71
|
+
.from('docbuilder_access')
|
|
72
|
+
.select('*')
|
|
73
|
+
.eq('user_id', user.id)
|
|
74
|
+
.eq('site_id', '${this.config.siteId}')
|
|
75
|
+
.single();
|
|
76
|
+
|
|
77
|
+
if (accessError || !access) {
|
|
78
|
+
showAccessDenied();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// User is authenticated and has access
|
|
83
|
+
console.log('User authenticated and authorized');
|
|
84
|
+
document.body.classList.add('authenticated');
|
|
85
|
+
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Auth check failed:', error);
|
|
88
|
+
redirectToLogin();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Redirect to login page
|
|
93
|
+
function redirectToLogin() {
|
|
94
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
95
|
+
const loginUrl = '/login.html' + (currentUrl !== '/' ? '?redirect=' + encodeURIComponent(currentUrl) : '');
|
|
96
|
+
window.location.href = loginUrl;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Show access denied message
|
|
100
|
+
function showAccessDenied() {
|
|
101
|
+
document.body.classList.add('authenticated'); // Show the body
|
|
102
|
+
document.body.innerHTML = \`
|
|
103
|
+
<div style="display: flex; justify-content: center; align-items: center; height: 100vh; font-family: Inter, sans-serif;">
|
|
104
|
+
<div style="text-align: center; max-width: 400px;">
|
|
105
|
+
<h1 style="color: #ef4444; margin-bottom: 1rem;">Access Denied</h1>
|
|
106
|
+
<p style="color: #6b7280; margin-bottom: 2rem;">You don't have permission to view this documentation site.</p>
|
|
107
|
+
<a href="/login.html" style="background: #3b82f6; color: white; padding: 0.75rem 1.5rem; border-radius: 0.5rem; text-decoration: none;">Try Different Account</a>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
\`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Add logout functionality
|
|
114
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
115
|
+
const logoutLinks = document.querySelectorAll('a[href*="logout"]');
|
|
116
|
+
logoutLinks.forEach(link => {
|
|
117
|
+
link.addEventListener('click', async function(e) {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
await supabaseClient.auth.signOut();
|
|
120
|
+
window.location.href = '/logout.html';
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Run auth check
|
|
126
|
+
checkAuth();
|
|
127
|
+
|
|
128
|
+
})();
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate login page HTML
|
|
134
|
+
*/
|
|
135
|
+
generateLoginPage(config) {
|
|
136
|
+
const siteName = config.siteName || 'Documentation';
|
|
137
|
+
|
|
138
|
+
return `<!DOCTYPE html>
|
|
139
|
+
<html lang="en">
|
|
140
|
+
<head>
|
|
141
|
+
<meta charset="UTF-8">
|
|
142
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
143
|
+
<title>Login - ${siteName}</title>
|
|
144
|
+
<link rel="stylesheet" href="css/notion-style.css">
|
|
145
|
+
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
|
146
|
+
</head>
|
|
147
|
+
<body class="auth-page">
|
|
148
|
+
<div class="auth-container">
|
|
149
|
+
<div class="auth-box">
|
|
150
|
+
<h1>Login to ${siteName}</h1>
|
|
151
|
+
<form id="login-form">
|
|
152
|
+
<div class="form-group">
|
|
153
|
+
<label for="email">Email</label>
|
|
154
|
+
<input type="email" id="email" name="email" required>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="form-group">
|
|
157
|
+
<label for="password">Password</label>
|
|
158
|
+
<input type="password" id="password" name="password" required>
|
|
159
|
+
</div>
|
|
160
|
+
<button type="submit" class="auth-button">Login</button>
|
|
161
|
+
</form>
|
|
162
|
+
<div id="error-message" class="error-message"></div>
|
|
163
|
+
<div class="auth-links">
|
|
164
|
+
<a href="#" id="forgot-password">Forgot Password?</a>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<script>
|
|
170
|
+
// Initialize Supabase
|
|
171
|
+
const { createClient } = supabase;
|
|
172
|
+
const supabaseClient = createClient('${this.config.supabaseUrl}', '${this.config.supabaseAnonKey}');
|
|
173
|
+
|
|
174
|
+
// Handle login form
|
|
175
|
+
document.getElementById('login-form').addEventListener('submit', async function(e) {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
|
|
178
|
+
const email = document.getElementById('email').value;
|
|
179
|
+
const password = document.getElementById('password').value;
|
|
180
|
+
const errorDiv = document.getElementById('error-message');
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Sign in with Supabase
|
|
184
|
+
const { data, error } = await supabaseClient.auth.signInWithPassword({
|
|
185
|
+
email: email,
|
|
186
|
+
password: password
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (error) throw error;
|
|
190
|
+
|
|
191
|
+
// Check if user has access to this site
|
|
192
|
+
const { data: access, error: accessError } = await supabaseClient
|
|
193
|
+
.from('docbuilder_access')
|
|
194
|
+
.select('*')
|
|
195
|
+
.eq('user_id', data.user.id)
|
|
196
|
+
.eq('site_id', '${this.config.siteId}')
|
|
197
|
+
.single();
|
|
198
|
+
|
|
199
|
+
if (accessError || !access) {
|
|
200
|
+
await supabaseClient.auth.signOut();
|
|
201
|
+
throw new Error('You do not have access to this documentation site');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Redirect to requested page
|
|
205
|
+
const params = new URLSearchParams(window.location.search);
|
|
206
|
+
const redirect = params.get('redirect') || '/';
|
|
207
|
+
window.location.href = redirect;
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
errorDiv.textContent = error.message;
|
|
211
|
+
errorDiv.style.display = 'block';
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Handle forgot password
|
|
216
|
+
document.getElementById('forgot-password').addEventListener('click', async function(e) {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
|
|
219
|
+
const email = document.getElementById('email').value;
|
|
220
|
+
if (!email) {
|
|
221
|
+
alert('Please enter your email address first');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const { error } = await supabaseClient.auth.resetPasswordForEmail(email, {
|
|
227
|
+
redirectTo: window.location.origin + '/login.html'
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (error) throw error;
|
|
231
|
+
|
|
232
|
+
alert('Password reset email sent! Check your inbox.');
|
|
233
|
+
} catch (error) {
|
|
234
|
+
alert('Error sending reset email: ' + error.message);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
</script>
|
|
238
|
+
</body>
|
|
239
|
+
</html>`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Generate logout page HTML
|
|
244
|
+
*/
|
|
245
|
+
generateLogoutPage(config) {
|
|
246
|
+
const siteName = config.siteName || 'Documentation';
|
|
247
|
+
|
|
248
|
+
return `<!DOCTYPE html>
|
|
249
|
+
<html lang="en">
|
|
250
|
+
<head>
|
|
251
|
+
<meta charset="UTF-8">
|
|
252
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
253
|
+
<title>Logged Out - ${siteName}</title>
|
|
254
|
+
<link rel="stylesheet" href="css/notion-style.css">
|
|
255
|
+
</head>
|
|
256
|
+
<body class="auth-page">
|
|
257
|
+
<div class="auth-container">
|
|
258
|
+
<div class="auth-box">
|
|
259
|
+
<h1>You have been logged out</h1>
|
|
260
|
+
<p>Thank you for using ${siteName}.</p>
|
|
261
|
+
<a href="login.html" class="auth-button">Login Again</a>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</body>
|
|
265
|
+
</html>`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Validate Supabase configuration
|
|
270
|
+
*/
|
|
271
|
+
static validateConfig(config) {
|
|
272
|
+
const errors = [];
|
|
273
|
+
|
|
274
|
+
if (!config.auth.supabaseUrl) {
|
|
275
|
+
errors.push('auth.supabaseUrl is required');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!config.auth.supabaseAnonKey) {
|
|
279
|
+
errors.push('auth.supabaseAnonKey is required');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!config.auth.siteId) {
|
|
283
|
+
errors.push('auth.siteId is required');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Validate URL format
|
|
287
|
+
if (config.auth.supabaseUrl && !config.auth.supabaseUrl.match(/^https:\/\/\w+\.supabase\.co$/)) {
|
|
288
|
+
errors.push('auth.supabaseUrl must be a valid Supabase URL (https://xxx.supabase.co)');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return errors;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = SupabaseAuth;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowcode/doc-builder",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.5",
|
|
4
4
|
"description": "Reusable documentation builder for markdown-based sites with Vercel deployment support",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"homepage": "https://github.com/wapdat/doc-builder#readme",
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@knowcode/doc-builder": "^1.4.21",
|
|
35
|
+
"@supabase/supabase-js": "^2.39.0",
|
|
35
36
|
"chalk": "^4.1.2",
|
|
36
37
|
"commander": "^11.0.0",
|
|
37
38
|
"fs-extra": "^11.2.0",
|
package/public-config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
siteName: '✨ Test Documentation',
|
|
3
|
+
siteDescription: 'Public documentation site',
|
|
4
|
+
favicon: '✨',
|
|
5
|
+
footer: {
|
|
6
|
+
copyright: 'Test Documentation',
|
|
7
|
+
links: []
|
|
8
|
+
},
|
|
9
|
+
docsDir: 'test-docs',
|
|
10
|
+
outputDir: 'public-html',
|
|
11
|
+
// No authentication configured - this is a public site
|
|
12
|
+
features: {
|
|
13
|
+
authentication: false,
|
|
14
|
+
darkMode: true,
|
|
15
|
+
mermaid: true,
|
|
16
|
+
changelog: false,
|
|
17
|
+
normalizeTitle: true,
|
|
18
|
+
showPdfDownload: true,
|
|
19
|
+
menuDefaultOpen: true,
|
|
20
|
+
phosphorIcons: true
|
|
21
|
+
}
|
|
22
|
+
};
|