@kyro-cms/admin 0.3.2 → 0.3.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/dist/EditorClient-XEUOVAAC.js +466 -0
- package/dist/EditorClient-XEUOVAAC.js.map +1 -0
- package/dist/EditorClient-YLCGVDXY.cjs +468 -0
- package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
- package/dist/chunk-7KPIUCGT.js +384 -0
- package/dist/chunk-7KPIUCGT.js.map +1 -0
- package/dist/chunk-GOACG6R7.cjs +473 -0
- package/dist/chunk-GOACG6R7.cjs.map +1 -0
- package/dist/index.cjs +14861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1661 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +563 -0
- package/dist/index.js +14784 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -19
- package/src/components/ActionBar.tsx +7 -43
- package/src/components/Admin.tsx +138 -277
- package/src/components/ApiKeysManager.tsx +428 -419
- package/src/components/AuditLogsPage.tsx +35 -39
- package/src/components/AuthBridge.tsx +51 -0
- package/src/components/AutoForm.tsx +495 -1230
- package/src/components/BrandingHub.tsx +18 -19
- package/src/components/BulkActionsBar.tsx +1 -1
- package/src/components/CreateView.tsx +22 -36
- package/src/components/Dashboard.tsx +60 -84
- package/src/components/DetailView.tsx +113 -91
- package/src/components/DeveloperCenter.tsx +200 -198
- package/src/components/FieldRenderer.tsx +206 -0
- package/src/components/GraphQLPlayground.tsx +340 -480
- package/src/components/ListView.tsx +828 -254
- package/src/components/LoginPage.tsx +3 -4
- package/src/components/MarketplaceManager.tsx +254 -0
- package/src/components/MediaGallery.tsx +856 -1192
- package/src/components/PluginsManager.tsx +277 -0
- package/src/components/RestPlayground.tsx +398 -560
- package/src/components/SessionsManager.tsx +211 -0
- package/src/components/Sidebar.astro +179 -151
- package/src/components/ThemeProvider.tsx +7 -161
- package/src/components/UserManagement.tsx +162 -146
- package/src/components/UserMenu.tsx +110 -0
- package/src/components/WebhookManager.tsx +305 -367
- package/src/components/blocks/AccordionBlock.tsx +4 -4
- package/src/components/blocks/ArrayBlock.tsx +3 -3
- package/src/components/blocks/BlockEditModal.tsx +8 -8
- package/src/components/blocks/BlockWrapper.tsx +61 -0
- package/src/components/blocks/ButtonBlock.tsx +4 -4
- package/src/components/blocks/ChildBlocksTree.tsx +23 -25
- package/src/components/blocks/CodeBlock.tsx +15 -15
- package/src/components/blocks/ColumnsBlock.tsx +6 -44
- package/src/components/blocks/DividerBlock.tsx +3 -3
- package/src/components/blocks/FileBlock.tsx +4 -4
- package/src/components/blocks/HeadingBlock.tsx +6 -38
- package/src/components/blocks/HeroBlock.tsx +4 -4
- package/src/components/blocks/ImageBlock.tsx +4 -4
- package/src/components/blocks/LinkBlock.tsx +4 -4
- package/src/components/blocks/ListBlock.tsx +3 -3
- package/src/components/blocks/ParagraphBlock.tsx +12 -42
- package/src/components/blocks/RelationshipBlock.tsx +4 -4
- package/src/components/blocks/RichTextBlock.tsx +4 -4
- package/src/components/blocks/VStackBlock.tsx +5 -37
- package/src/components/blocks/VideoBlock.tsx +4 -4
- package/src/components/blocks/types.ts +11 -0
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayField.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +93 -0
- package/src/components/fields/BlocksField.tsx +122 -111
- package/src/components/fields/ButtonField.tsx +1 -1
- package/src/components/fields/CheckboxField.tsx +14 -15
- package/src/components/fields/ChildrenField.tsx +2 -2
- package/src/components/fields/CodeField.tsx +3 -3
- package/src/components/fields/ColumnsField.tsx +2 -2
- package/src/components/fields/DateField.tsx +13 -26
- package/src/components/fields/EditorClient.tsx +26 -28
- package/src/components/fields/FieldLayout.tsx +52 -0
- package/src/components/fields/GroupLayout.tsx +35 -0
- package/src/components/fields/JSONField.tsx +7 -7
- package/src/components/fields/LinkField.tsx +1 -1
- package/src/components/fields/MarkdownField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +13 -26
- package/src/components/fields/PortableTextField.tsx +4 -4
- package/src/components/fields/PortableTextRenderer.tsx +1 -1
- package/src/components/fields/RelationshipBlockField.tsx +31 -23
- package/src/components/fields/RelationshipField.tsx +14 -14
- package/src/components/fields/SelectField.tsx +17 -26
- package/src/components/fields/TabsLayout.tsx +69 -0
- package/src/components/fields/TextField.tsx +85 -38
- package/src/components/fields/UploadField.tsx +71 -41
- package/src/components/fields/VideoField.tsx +1 -1
- package/src/components/fields/extensions/blockComponents.tsx +2 -2
- package/src/components/fields/extensions/blocksStore.ts +207 -193
- package/src/components/fields/types.ts +22 -0
- package/src/components/layout/Layout.tsx +1 -1
- package/src/components/ui/ActionMenu.tsx +63 -0
- package/src/components/ui/Badge.tsx +59 -5
- package/src/components/ui/BlockDrawer.tsx +4 -5
- package/src/components/ui/CommandPalette.tsx +58 -36
- package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
- package/src/components/ui/Dropdown.tsx +18 -16
- package/src/components/ui/EmptyState.tsx +25 -0
- package/src/components/ui/GlobalModal.tsx +49 -0
- package/src/components/ui/IconButton.tsx +44 -0
- package/src/components/ui/Modal.tsx +19 -20
- package/src/components/ui/PageHeader.tsx +158 -0
- package/src/components/ui/Pagination.tsx +61 -0
- package/src/components/ui/PromptModal.tsx +1 -1
- package/src/components/ui/SearchInput.tsx +57 -0
- package/src/components/ui/SeoPreview.tsx +31 -0
- package/src/components/ui/SessionModal.tsx +0 -0
- package/src/components/ui/SlidePanel.tsx +2 -0
- package/src/components/ui/Toast.tsx +65 -122
- package/src/components/ui/Toaster.tsx +18 -0
- package/src/components/ui/icons.tsx +112 -0
- package/src/components/users/UserDetail.tsx +290 -0
- package/src/components/users/UserForm.tsx +242 -0
- package/src/components/users/UsersList.tsx +338 -0
- package/src/env.d.ts +13 -13
- package/src/fields/index.ts +2 -1
- package/src/global.d.ts +7 -0
- package/src/hooks/data.ts +2 -9
- package/src/hooks/useAsyncData.ts +36 -0
- package/src/hooks/useAutoFormState.ts +527 -0
- package/src/hooks/useSelection.ts +49 -0
- package/src/hooks/useSession.ts +0 -0
- package/src/index.ts +11 -1
- package/src/integration.ts +86 -11
- package/src/kyro-cms.d.ts +209 -0
- package/src/layouts/AdminLayout.astro +128 -11
- package/src/layouts/AuthLayout.astro +21 -5
- package/src/lib/api.ts +175 -55
- package/src/lib/autoform-store.ts +435 -0
- package/src/lib/config.ts +82 -34
- package/src/lib/createRegistry.ts +29 -0
- package/src/lib/default-kyro-config.ts +4 -0
- package/src/lib/globals.ts +50 -0
- package/src/lib/media-utils.ts +18 -0
- package/src/lib/object-utils.ts +77 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/stores/index.ts +370 -0
- package/src/lib/types.ts +43 -0
- package/src/lib/useResourceManager.ts +105 -0
- package/src/pages/403.astro +67 -0
- package/src/pages/[collection]/[id].astro +14 -180
- package/src/pages/[collection]/index.astro +11 -6
- package/src/pages/api-explorer.astro +173 -0
- package/src/pages/audit/index.astro +2 -0
- package/src/pages/auth/login.astro +122 -0
- package/src/pages/auth/register.astro +167 -0
- package/src/pages/graphql-explorer.astro +59 -0
- package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
- package/src/pages/index.astro +577 -0
- package/src/pages/index_ALT.astro +3 -0
- package/src/pages/keys.astro +11 -0
- package/src/pages/marketplace.astro +11 -0
- package/src/pages/media.astro +3 -0
- package/src/pages/plugins.astro +8 -0
- package/src/pages/preview/[collection]/[id].astro +188 -123
- package/src/pages/rest-playground.astro +62 -0
- package/src/pages/roles/index.astro +183 -76
- package/src/pages/sessions.astro +8 -0
- package/src/pages/settings/[slug].astro +92 -114
- package/src/pages/settings/index.astro +5 -3
- package/src/pages/users/[id].astro +25 -154
- package/src/pages/users/index.astro +19 -130
- package/src/pages/users/new.astro +9 -86
- package/src/pages/webhooks.astro +11 -0
- package/src/routes.ts +80 -0
- package/src/styles/main.css +119 -79
- package/src/theme/tokens.ts +1 -0
- package/src/vite-env.d.ts +14 -0
- package/src/collections/auth/index.ts +0 -155
- package/src/collections/portfolio/index.ts +0 -343
- package/src/components/ApiExplorer.tsx +0 -325
- package/src/components/EnhancedListView.tsx +0 -889
- package/src/components/GraphQLExplorer.tsx +0 -675
- package/src/components/Icons.tsx +0 -23
- package/src/components/StatusBadge.tsx +0 -76
- package/src/lib/MediaService.ts +0 -541
- package/src/lib/auth/sqlite-adapter.ts +0 -319
- package/src/lib/dataStore.ts +0 -226
- package/src/lib/db/adapter.ts +0 -54
- package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
- package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
- package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
- package/src/lib/db/index.ts +0 -449
- package/src/lib/db/mongodb-adapter.ts +0 -207
- package/src/lib/db/mongodb-auth-adapter.ts +0 -305
- package/src/lib/db/schema/mysql-auth.ts +0 -113
- package/src/lib/db/schema/mysql-content.ts +0 -20
- package/src/lib/db/schema/postgres-auth.ts +0 -116
- package/src/lib/db/schema/postgres-content.ts +0 -35
- package/src/lib/db/schema/postgres-media.ts +0 -52
- package/src/lib/db/schema/postgres-settings.ts +0 -11
- package/src/lib/db/schema/sqlite-auth.ts +0 -112
- package/src/lib/db/schema/sqlite-content.ts +0 -20
- package/src/lib/db/version-adapter.ts +0 -248
- package/src/lib/graphql/index.ts +0 -1
- package/src/lib/graphql/schema.ts +0 -443
- package/src/lib/rate-limit.ts +0 -267
- package/src/lib/storage.ts +0 -374
- package/src/lib/store.ts +0 -85
- package/src/middleware.ts +0 -177
- package/src/pages/admin/api-explorer.astro +0 -98
- package/src/pages/admin/graphql-explorer.astro +0 -40
- package/src/pages/admin/index.astro +0 -286
- package/src/pages/admin/keys.astro +0 -8
- package/src/pages/admin/rest-playground.astro +0 -44
- package/src/pages/admin/webhooks.astro +0 -8
- package/src/pages/api/[collection]/[id]/publish.ts +0 -52
- package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
- package/src/pages/api/[collection]/[id]/versions.ts +0 -66
- package/src/pages/api/[collection]/[id].ts +0 -213
- package/src/pages/api/[collection]/index.ts +0 -209
- package/src/pages/api/auth/[id].ts +0 -121
- package/src/pages/api/auth/audit-logs.ts +0 -57
- package/src/pages/api/auth/login.ts +0 -211
- package/src/pages/api/auth/logout.ts +0 -66
- package/src/pages/api/auth/me.ts +0 -36
- package/src/pages/api/auth/refresh.ts +0 -119
- package/src/pages/api/auth/register.ts +0 -188
- package/src/pages/api/auth/users.ts +0 -97
- package/src/pages/api/collections.ts +0 -59
- package/src/pages/api/globals/[slug].ts +0 -42
- package/src/pages/api/graphql.ts +0 -90
- package/src/pages/api/health.ts +0 -426
- package/src/pages/api/keys/[id].ts +0 -26
- package/src/pages/api/keys/index.ts +0 -75
- package/src/pages/api/media/[id].ts +0 -309
- package/src/pages/api/media/folders.ts +0 -609
- package/src/pages/api/media/index.ts +0 -146
- package/src/pages/api/media/resize.ts +0 -267
- package/src/pages/api/search.ts +0 -82
- package/src/pages/api/slug-availability.ts +0 -70
- package/src/pages/api/storage-config.ts +0 -20
- package/src/pages/api/storage-status.ts +0 -206
- package/src/pages/api/upload.ts +0 -334
- package/src/pages/api/webhooks/index.ts +0 -71
- package/src/pages/login.astro +0 -82
- package/src/pages/register.astro +0 -102
|
@@ -1,548 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AuthAdapter,
|
|
3
|
-
AuthUser,
|
|
4
|
-
Session,
|
|
5
|
-
UserRole,
|
|
6
|
-
AuditLog,
|
|
7
|
-
AuditLogFilter,
|
|
8
|
-
} from "@kyro-cms/core";
|
|
9
|
-
import Database from "better-sqlite3";
|
|
10
|
-
import path from "path";
|
|
11
|
-
import fs from "fs";
|
|
12
|
-
import bcrypt from "bcryptjs";
|
|
13
|
-
import { randomBytes } from "crypto";
|
|
14
|
-
|
|
15
|
-
function getAuthDbPath(): string {
|
|
16
|
-
return (
|
|
17
|
-
process.env.AUTH_DB_PATH || path.join(process.cwd(), "data", "auth.db")
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function ensureDbDir(dbPath: string): void {
|
|
22
|
-
const dir = path.dirname(dbPath);
|
|
23
|
-
if (!fs.existsSync(dir)) {
|
|
24
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getAuthDb() {
|
|
29
|
-
const dbPath = getAuthDbPath();
|
|
30
|
-
ensureDbDir(dbPath);
|
|
31
|
-
const db = new Database(dbPath);
|
|
32
|
-
db.pragma("journal_mode = WAL");
|
|
33
|
-
db.pragma("synchronous = NORMAL");
|
|
34
|
-
db.pragma("cache_size = -64000");
|
|
35
|
-
db.pragma("foreign_keys = ON");
|
|
36
|
-
return db;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const db = getAuthDb();
|
|
40
|
-
|
|
41
|
-
db.exec(`
|
|
42
|
-
CREATE TABLE IF NOT EXISTS users (
|
|
43
|
-
id TEXT PRIMARY KEY,
|
|
44
|
-
email TEXT UNIQUE NOT NULL,
|
|
45
|
-
password_hash TEXT NOT NULL,
|
|
46
|
-
name TEXT,
|
|
47
|
-
role TEXT NOT NULL DEFAULT 'customer',
|
|
48
|
-
tenant_id TEXT,
|
|
49
|
-
email_verified INTEGER DEFAULT 0,
|
|
50
|
-
locked INTEGER DEFAULT 0,
|
|
51
|
-
last_login TEXT,
|
|
52
|
-
failed_login_attempts INTEGER DEFAULT 0,
|
|
53
|
-
locked_until TEXT,
|
|
54
|
-
created_at TEXT NOT NULL,
|
|
55
|
-
updated_at TEXT NOT NULL
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
59
|
-
id TEXT PRIMARY KEY,
|
|
60
|
-
token TEXT UNIQUE NOT NULL,
|
|
61
|
-
refresh_token TEXT,
|
|
62
|
-
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
63
|
-
expires_at TEXT NOT NULL,
|
|
64
|
-
created_at TEXT NOT NULL
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
CREATE TABLE IF NOT EXISTS roles (
|
|
68
|
-
id TEXT PRIMARY KEY,
|
|
69
|
-
name TEXT UNIQUE NOT NULL,
|
|
70
|
-
level INTEGER NOT NULL DEFAULT 0,
|
|
71
|
-
inherits TEXT,
|
|
72
|
-
permissions TEXT,
|
|
73
|
-
is_system INTEGER DEFAULT 0,
|
|
74
|
-
description TEXT,
|
|
75
|
-
created_at TEXT NOT NULL
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
79
|
-
id TEXT PRIMARY KEY,
|
|
80
|
-
action TEXT NOT NULL,
|
|
81
|
-
user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
|
82
|
-
user_email TEXT,
|
|
83
|
-
role TEXT,
|
|
84
|
-
resource TEXT NOT NULL,
|
|
85
|
-
ip_address TEXT,
|
|
86
|
-
user_agent TEXT,
|
|
87
|
-
success INTEGER NOT NULL,
|
|
88
|
-
error TEXT,
|
|
89
|
-
metadata TEXT,
|
|
90
|
-
created_at TEXT NOT NULL
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
CREATE TABLE IF NOT EXISTS rate_limits (
|
|
94
|
-
id TEXT PRIMARY KEY,
|
|
95
|
-
identifier TEXT NOT NULL,
|
|
96
|
-
action TEXT NOT NULL,
|
|
97
|
-
attempts INTEGER NOT NULL DEFAULT 1,
|
|
98
|
-
first_attempt TEXT,
|
|
99
|
-
last_attempt TEXT,
|
|
100
|
-
expires_at TEXT,
|
|
101
|
-
created_at TEXT NOT NULL
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
|
105
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
|
|
106
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
|
|
107
|
-
CREATE INDEX IF NOT EXISTS idx_audit_user_id ON audit_logs(user_id);
|
|
108
|
-
CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_logs(action);
|
|
109
|
-
CREATE INDEX IF NOT EXISTS idx_audit_created_at ON audit_logs(created_at);
|
|
110
|
-
CREATE INDEX IF NOT EXISTS idx_rate_identifier ON rate_limits(identifier);
|
|
111
|
-
CREATE INDEX IF NOT EXISTS idx_rate_expires ON rate_limits(expires_at);
|
|
112
|
-
`);
|
|
113
|
-
|
|
114
|
-
export interface DrizzleSQLiteAuthAdapterOptions {
|
|
115
|
-
tokenExpiration?: number;
|
|
116
|
-
refreshTokenExpiration?: number;
|
|
117
|
-
saltRounds?: number;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const DEFAULT_TOKEN_EXPIRATION = 86400;
|
|
121
|
-
const DEFAULT_REFRESH_EXPIRATION = 604800;
|
|
122
|
-
|
|
123
|
-
export class DrizzleSQLiteAuthAdapter implements AuthAdapter {
|
|
124
|
-
private tokenExpiration: number;
|
|
125
|
-
private refreshExpiration: number;
|
|
126
|
-
private saltRounds: number;
|
|
127
|
-
|
|
128
|
-
constructor(options: DrizzleSQLiteAuthAdapterOptions = {}) {
|
|
129
|
-
this.tokenExpiration = options.tokenExpiration || DEFAULT_TOKEN_EXPIRATION;
|
|
130
|
-
this.refreshExpiration =
|
|
131
|
-
options.refreshTokenExpiration || DEFAULT_REFRESH_EXPIRATION;
|
|
132
|
-
this.saltRounds = options.saltRounds || 10;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private getTimestamp(): string {
|
|
136
|
-
return new Date().toISOString();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private generateId(): string {
|
|
140
|
-
return randomBytes(16).toString("hex");
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async findUserById(id: string): Promise<AuthUser | null> {
|
|
144
|
-
const user = db.prepare("SELECT * FROM users WHERE id = ?").get(id) as any;
|
|
145
|
-
if (!user) return null;
|
|
146
|
-
return {
|
|
147
|
-
id: user.id,
|
|
148
|
-
email: user.email,
|
|
149
|
-
name: user.name || undefined,
|
|
150
|
-
role: user.role as UserRole,
|
|
151
|
-
tenantId: user.tenant_id || undefined,
|
|
152
|
-
createdAt: user.created_at,
|
|
153
|
-
updatedAt: user.updated_at,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async findUserByEmail(email: string): Promise<AuthUser | null> {
|
|
158
|
-
if (!email) return null;
|
|
159
|
-
const user = db
|
|
160
|
-
.prepare("SELECT * FROM users WHERE LOWER(email) = LOWER(?)")
|
|
161
|
-
.get(email) as any;
|
|
162
|
-
if (!user) return null;
|
|
163
|
-
return {
|
|
164
|
-
id: user.id,
|
|
165
|
-
email: user.email,
|
|
166
|
-
name: user.name || undefined,
|
|
167
|
-
role: user.role as UserRole,
|
|
168
|
-
tenantId: user.tenant_id || undefined,
|
|
169
|
-
createdAt: user.created_at,
|
|
170
|
-
updatedAt: user.updated_at,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async findAllUsers(): Promise<AuthUser[]> {
|
|
175
|
-
const users = db
|
|
176
|
-
.prepare("SELECT * FROM users ORDER BY created_at DESC")
|
|
177
|
-
.all() as any[];
|
|
178
|
-
return users.map((user) => ({
|
|
179
|
-
id: user.id,
|
|
180
|
-
email: user.email,
|
|
181
|
-
name: user.name || undefined,
|
|
182
|
-
role: user.role as UserRole,
|
|
183
|
-
tenantId: user.tenant_id || undefined,
|
|
184
|
-
emailVerified: !!user.email_verified,
|
|
185
|
-
locked: !!user.locked,
|
|
186
|
-
lastLogin: user.last_login || undefined,
|
|
187
|
-
createdAt: user.created_at,
|
|
188
|
-
updatedAt: user.updated_at,
|
|
189
|
-
}));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async createUser(userData: {
|
|
193
|
-
email: string;
|
|
194
|
-
password: string;
|
|
195
|
-
role?: UserRole;
|
|
196
|
-
tenantId?: string;
|
|
197
|
-
}): Promise<AuthUser> {
|
|
198
|
-
const now = this.getTimestamp();
|
|
199
|
-
const id = this.generateId();
|
|
200
|
-
const passwordHash = await bcrypt.hash(userData.password, this.saltRounds);
|
|
201
|
-
|
|
202
|
-
db.prepare(
|
|
203
|
-
"INSERT INTO users (id, email, password_hash, name, role, tenant_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
204
|
-
).run(
|
|
205
|
-
id,
|
|
206
|
-
userData.email.toLowerCase(),
|
|
207
|
-
passwordHash,
|
|
208
|
-
null,
|
|
209
|
-
userData.role || "customer",
|
|
210
|
-
userData.tenantId || null,
|
|
211
|
-
now,
|
|
212
|
-
now,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
id,
|
|
217
|
-
email: userData.email,
|
|
218
|
-
role: userData.role || "customer",
|
|
219
|
-
tenantId: userData.tenantId,
|
|
220
|
-
createdAt: now,
|
|
221
|
-
updatedAt: now,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async updateUser(
|
|
226
|
-
id: string,
|
|
227
|
-
data: Partial<AuthUser> & { password?: string },
|
|
228
|
-
): Promise<AuthUser | null> {
|
|
229
|
-
const existing = await this.findUserById(id);
|
|
230
|
-
if (!existing) return null;
|
|
231
|
-
|
|
232
|
-
const now = this.getTimestamp();
|
|
233
|
-
const updates: string[] = ["updated_at = ?"];
|
|
234
|
-
const values: any[] = [now];
|
|
235
|
-
|
|
236
|
-
if (data.email) {
|
|
237
|
-
updates.push("email = ?");
|
|
238
|
-
values.push(data.email.toLowerCase());
|
|
239
|
-
}
|
|
240
|
-
if (data.name !== undefined) {
|
|
241
|
-
updates.push("name = ?");
|
|
242
|
-
values.push(data.name);
|
|
243
|
-
}
|
|
244
|
-
if (data.role) {
|
|
245
|
-
updates.push("role = ?");
|
|
246
|
-
values.push(data.role);
|
|
247
|
-
}
|
|
248
|
-
if (data.tenantId !== undefined) {
|
|
249
|
-
updates.push("tenant_id = ?");
|
|
250
|
-
values.push(data.tenantId);
|
|
251
|
-
}
|
|
252
|
-
if (data.password) {
|
|
253
|
-
updates.push("password_hash = ?");
|
|
254
|
-
values.push(await bcrypt.hash(data.password, this.saltRounds));
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
values.push(id);
|
|
258
|
-
db.prepare(`UPDATE users SET ${updates.join(", ")} WHERE id = ?`).run(
|
|
259
|
-
...values,
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
return this.findUserById(id);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async deleteUser(id: string): Promise<boolean> {
|
|
266
|
-
db.prepare("DELETE FROM users WHERE id = ?").run(id);
|
|
267
|
-
return true;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
async verifyPassword(
|
|
271
|
-
email: string,
|
|
272
|
-
password: string,
|
|
273
|
-
): Promise<AuthUser | null> {
|
|
274
|
-
const user = await this.findUserByEmail(email);
|
|
275
|
-
if (!user) return null;
|
|
276
|
-
|
|
277
|
-
const stored = db
|
|
278
|
-
.prepare("SELECT password_hash FROM users WHERE id = ?")
|
|
279
|
-
.get(user.id) as { password_hash: string } | undefined;
|
|
280
|
-
if (!stored?.password_hash) return null;
|
|
281
|
-
|
|
282
|
-
const valid = await bcrypt.compare(password, stored.password_hash);
|
|
283
|
-
if (!valid) return null;
|
|
284
|
-
|
|
285
|
-
return user;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async hashPassword(password: string): Promise<string> {
|
|
289
|
-
return bcrypt.hash(password, this.saltRounds);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async createSession(userId: string): Promise<Session> {
|
|
293
|
-
const now = new Date();
|
|
294
|
-
const expiresAt = new Date(now.getTime() + this.tokenExpiration * 1000);
|
|
295
|
-
|
|
296
|
-
const id = this.generateId();
|
|
297
|
-
const token = randomBytes(32).toString("hex");
|
|
298
|
-
const refreshToken = randomBytes(32).toString("hex");
|
|
299
|
-
|
|
300
|
-
db.prepare(
|
|
301
|
-
"INSERT INTO sessions (id, token, refresh_token, user_id, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
302
|
-
).run(
|
|
303
|
-
id,
|
|
304
|
-
token,
|
|
305
|
-
refreshToken,
|
|
306
|
-
userId,
|
|
307
|
-
expiresAt.toISOString(),
|
|
308
|
-
now.toISOString(),
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
token,
|
|
313
|
-
refreshToken,
|
|
314
|
-
expiresAt: expiresAt.toISOString(),
|
|
315
|
-
userId,
|
|
316
|
-
createdAt: now.toISOString(),
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
async findSessionByToken(token: string): Promise<Session | null> {
|
|
321
|
-
const now = new Date().toISOString();
|
|
322
|
-
const session = db
|
|
323
|
-
.prepare("SELECT * FROM sessions WHERE token = ? AND expires_at > ?")
|
|
324
|
-
.get(token, now) as any;
|
|
325
|
-
if (!session) return null;
|
|
326
|
-
return {
|
|
327
|
-
token: session.token,
|
|
328
|
-
refreshToken: session.refresh_token || undefined,
|
|
329
|
-
userId: session.user_id,
|
|
330
|
-
expiresAt: session.expires_at,
|
|
331
|
-
createdAt: session.created_at,
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async deleteSession(sessionId: string): Promise<boolean> {
|
|
336
|
-
db.prepare("DELETE FROM sessions WHERE token = ?").run(sessionId);
|
|
337
|
-
return true;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async deleteUserSessions(userId: string): Promise<number> {
|
|
341
|
-
db.prepare("DELETE FROM sessions WHERE user_id = ?").run(userId);
|
|
342
|
-
return 1;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async hasAnyUsers(): Promise<boolean> {
|
|
346
|
-
const result = db.prepare("SELECT COUNT(*) as cnt FROM users").get() as {
|
|
347
|
-
cnt: number;
|
|
348
|
-
};
|
|
349
|
-
return (result?.cnt || 0) > 0;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async findUserRoles(): Promise<UserRole[]> {
|
|
353
|
-
const roles = db.prepare("SELECT * FROM roles").all() as any[];
|
|
354
|
-
|
|
355
|
-
if (roles.length === 0) {
|
|
356
|
-
const defaultRoles = [
|
|
357
|
-
{
|
|
358
|
-
id: "super_admin",
|
|
359
|
-
name: "super_admin",
|
|
360
|
-
level: 100,
|
|
361
|
-
inherits: ["admin"],
|
|
362
|
-
permissions: [],
|
|
363
|
-
isSystem: 1,
|
|
364
|
-
description: "Full system access",
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
id: "admin",
|
|
368
|
-
name: "admin",
|
|
369
|
-
level: 90,
|
|
370
|
-
inherits: ["editor"],
|
|
371
|
-
permissions: [],
|
|
372
|
-
isSystem: 1,
|
|
373
|
-
description: "Full tenant access",
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
id: "editor",
|
|
377
|
-
name: "editor",
|
|
378
|
-
level: 70,
|
|
379
|
-
inherits: ["author"],
|
|
380
|
-
permissions: [],
|
|
381
|
-
isSystem: 1,
|
|
382
|
-
description: "Edit and publish content",
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
id: "author",
|
|
386
|
-
name: "author",
|
|
387
|
-
level: 50,
|
|
388
|
-
inherits: ["customer"],
|
|
389
|
-
permissions: [],
|
|
390
|
-
isSystem: 1,
|
|
391
|
-
description: "Create and edit own content",
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
id: "customer",
|
|
395
|
-
name: "customer",
|
|
396
|
-
level: 30,
|
|
397
|
-
inherits: [],
|
|
398
|
-
permissions: [],
|
|
399
|
-
isSystem: 1,
|
|
400
|
-
description: "Access own data",
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
id: "guest",
|
|
404
|
-
name: "guest",
|
|
405
|
-
level: 10,
|
|
406
|
-
inherits: [],
|
|
407
|
-
permissions: [],
|
|
408
|
-
isSystem: 1,
|
|
409
|
-
description: "Public read-only access",
|
|
410
|
-
},
|
|
411
|
-
];
|
|
412
|
-
|
|
413
|
-
const now = this.getTimestamp();
|
|
414
|
-
for (const role of defaultRoles) {
|
|
415
|
-
db.prepare(
|
|
416
|
-
"INSERT INTO roles (id, name, level, inherits, permissions, is_system, description, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
417
|
-
).run(
|
|
418
|
-
role.id,
|
|
419
|
-
role.name,
|
|
420
|
-
role.level,
|
|
421
|
-
JSON.stringify(role.inherits),
|
|
422
|
-
JSON.stringify(role.permissions),
|
|
423
|
-
role.isSystem,
|
|
424
|
-
role.description,
|
|
425
|
-
now,
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return defaultRoles as UserRole[];
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return roles.map((r) => ({
|
|
433
|
-
id: r.id,
|
|
434
|
-
name: r.name,
|
|
435
|
-
level: r.level,
|
|
436
|
-
inherits: r.inherits ? JSON.parse(r.inherits) : [],
|
|
437
|
-
permissions: r.permissions ? JSON.parse(r.permissions) : [],
|
|
438
|
-
description: r.description || undefined,
|
|
439
|
-
createdAt: r.created_at,
|
|
440
|
-
}));
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async findAuditLogs(
|
|
444
|
-
filter: AuditLogFilter,
|
|
445
|
-
): Promise<{ logs: AuditLog[]; total: number }> {
|
|
446
|
-
const { limit = 50, offset = 0, action, userId, success } = filter;
|
|
447
|
-
|
|
448
|
-
let query = "SELECT * FROM audit_logs";
|
|
449
|
-
let countQuery = "SELECT COUNT(*) as total FROM audit_logs";
|
|
450
|
-
const conditions: string[] = [];
|
|
451
|
-
const params: any[] = [];
|
|
452
|
-
|
|
453
|
-
if (action) {
|
|
454
|
-
if (Array.isArray(action)) {
|
|
455
|
-
conditions.push(`action IN (${action.map(() => "?").join(",")})`);
|
|
456
|
-
params.push(...action);
|
|
457
|
-
} else {
|
|
458
|
-
conditions.push("action = ?");
|
|
459
|
-
params.push(action);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
if (userId) {
|
|
464
|
-
conditions.push("user_id = ?");
|
|
465
|
-
params.push(userId);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (success !== undefined) {
|
|
469
|
-
conditions.push("success = ?");
|
|
470
|
-
params.push(success ? 1 : 0);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (conditions.length > 0) {
|
|
474
|
-
const where = ` WHERE ${conditions.join(" AND ")}`;
|
|
475
|
-
query += where;
|
|
476
|
-
countQuery += where;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
|
|
480
|
-
|
|
481
|
-
const logs = db.prepare(query).all(...params, limit, offset) as any[];
|
|
482
|
-
const totalResult = db.prepare(countQuery).get(...params) as {
|
|
483
|
-
total: number;
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
return {
|
|
487
|
-
logs: logs.map((l) => ({
|
|
488
|
-
id: l.id,
|
|
489
|
-
action: l.action as any,
|
|
490
|
-
userId: l.user_id,
|
|
491
|
-
userEmail: l.user_email,
|
|
492
|
-
role: l.role,
|
|
493
|
-
resource: l.resource,
|
|
494
|
-
ipAddress: l.ip_address,
|
|
495
|
-
userAgent: l.user_agent,
|
|
496
|
-
success: !!l.success,
|
|
497
|
-
error: l.error,
|
|
498
|
-
metadata: l.metadata ? JSON.parse(l.metadata) : undefined,
|
|
499
|
-
timestamp: new Date(l.created_at),
|
|
500
|
-
})),
|
|
501
|
-
total: totalResult.total,
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
async createAuditLog(
|
|
506
|
-
data: Omit<AuditLog, "id" | "timestamp">,
|
|
507
|
-
): Promise<AuditLog> {
|
|
508
|
-
const id = this.generateId();
|
|
509
|
-
const now = this.getTimestamp();
|
|
510
|
-
|
|
511
|
-
db.prepare(
|
|
512
|
-
`
|
|
513
|
-
INSERT INTO audit_logs (
|
|
514
|
-
id, action, user_id, user_email, role, resource,
|
|
515
|
-
ip_address, user_agent, success, error, metadata, created_at
|
|
516
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
517
|
-
`,
|
|
518
|
-
).run(
|
|
519
|
-
id,
|
|
520
|
-
data.action,
|
|
521
|
-
data.userId || null,
|
|
522
|
-
data.userEmail || null,
|
|
523
|
-
data.role || null,
|
|
524
|
-
data.resource,
|
|
525
|
-
data.ipAddress || null,
|
|
526
|
-
data.userAgent || null,
|
|
527
|
-
data.success ? 1 : 0,
|
|
528
|
-
data.error || null,
|
|
529
|
-
data.metadata ? JSON.stringify(data.metadata) : null,
|
|
530
|
-
now,
|
|
531
|
-
);
|
|
532
|
-
|
|
533
|
-
return {
|
|
534
|
-
...data,
|
|
535
|
-
id,
|
|
536
|
-
timestamp: new Date(now),
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
let sqliteAuthAdapter: DrizzleSQLiteAuthAdapter | null = null;
|
|
542
|
-
|
|
543
|
-
export function getSQLiteAuthAdapter(): DrizzleSQLiteAuthAdapter {
|
|
544
|
-
if (!sqliteAuthAdapter) {
|
|
545
|
-
sqliteAuthAdapter = new DrizzleSQLiteAuthAdapter();
|
|
546
|
-
}
|
|
547
|
-
return sqliteAuthAdapter;
|
|
548
|
-
}
|