@kyro-cms/core 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -18,8 +18,8 @@ Kyro is built for **Astro** from the ground up. Unlike other CMS solutions that
18
18
 
19
19
  ### Key Features
20
20
 
21
- - **Local-First** - SQLite for zero-config development, no external database needed
22
- - **Multi-Database** - SQLite, PostgreSQL, MySQL, MongoDB via unified adapter interface
21
+ - **Zero-Config Development** - SQLite by default for instant setup, no external dependencies
22
+ - **Multi-Database** - SQLite (dev), PostgreSQL, MySQL, MongoDB via unified adapter interface
23
23
  - **Multi-Protocol API** - REST, GraphQL, tRPC, and WebSocket from a single config
24
24
  - **Multi-Vendor** - Built-in tenant scoping and row-level access control
25
25
  - **E-Commerce Ready** - Products, orders, customers, inventory, coupons out of the box
@@ -83,7 +83,7 @@ export default defineConfig({
83
83
 
84
84
  ## Database Adapters
85
85
 
86
- ### SQLite (Local-First)
86
+ ### SQLite (Default for Development)
87
87
 
88
88
  ```typescript
89
89
  import { localAdapter } from "@kyro-cms/core";
@@ -93,7 +93,7 @@ const adapter = localAdapter({
93
93
  });
94
94
  ```
95
95
 
96
- Perfect for development and small projects. Zero configuration required.
96
+ Perfect for development and small projects. Zero configuration required - no external services needed.
97
97
 
98
98
  ### PostgreSQL/MySQL (Production)
99
99
 
@@ -292,14 +292,17 @@ const kyro = createKyro({
292
292
 
293
293
  ## Authentication
294
294
 
295
- Built-in JWT authentication with Redis sessions and PostgreSQL support:
295
+ Built-in JWT authentication with multiple storage options:
296
296
 
297
297
  ```typescript
298
- import { RedisAuthAdapter } from "@kyro-cms/core";
298
+ import { RedisAuthAdapter, SQLiteAuthAdapter } from "@kyro-cms/core";
299
299
  import { createAuthConfig } from "@kyro-cms/core";
300
300
 
301
- // Use Redis for sessions (recommended)
302
- const adapter = new RedisAuthAdapter({
301
+ // Use SQLite for development (zero-config, default)
302
+ const adapter = new SQLiteAuthAdapter({ path: "./data.db" });
303
+
304
+ // Use Redis for sessions (recommended for production)
305
+ const redisAdapter = new RedisAuthAdapter({
303
306
  url: process.env.REDIS_URL,
304
307
  tls: process.env.REDIS_TLS === "true",
305
308
  });
@@ -318,8 +321,7 @@ const { redis, routes, passwordPolicy, lockout } = authConfig;
318
321
  ### Authentication Features
319
322
 
320
323
  - **JWT Tokens** - 24h expiry with refresh token support
321
- - **Redis Sessions** - Fast session storage with Redis Cloud support
322
- - **PostgreSQL Storage** - Production-ready with Drizzle ORM
324
+ - **Multiple Storage Options** - SQLite (dev), Redis, PostgreSQL via unified adapter interface
323
325
  - **Password Policy** - 12+ chars, complexity requirements, history check
324
326
  - **Account Lockout** - 5 failed attempts → 15 minute lockout
325
327
  - **Rate Limiting** - Per-IP and per-user limits
package/dist/index.cjs CHANGED
@@ -15,14 +15,16 @@ var chunkHT6VE4NW_cjs = require('./chunk-HT6VE4NW.cjs');
15
15
  var chunkRLTG4YZM_cjs = require('./chunk-RLTG4YZM.cjs');
16
16
  require('./chunk-Q7SFCCGT.cjs');
17
17
  var zod = require('zod');
18
- var bcrypt = require('bcrypt');
18
+ var bcrypt2 = require('bcrypt');
19
19
  var jwt2 = require('jsonwebtoken');
20
+ var bcrypt = require('bcryptjs');
20
21
  var crypto = require('crypto');
21
22
 
22
23
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
23
24
 
24
- var bcrypt__default = /*#__PURE__*/_interopDefault(bcrypt);
25
+ var bcrypt2__default = /*#__PURE__*/_interopDefault(bcrypt2);
25
26
  var jwt2__default = /*#__PURE__*/_interopDefault(jwt2);
27
+ var bcrypt__default = /*#__PURE__*/_interopDefault(bcrypt);
26
28
 
27
29
  // src/registry/validator.ts
28
30
  var ConfigValidationError = class extends Error {
@@ -1999,6 +2001,277 @@ var defaultFieldStyling = {
1999
2001
  }
2000
2002
  }
2001
2003
  };
2004
+ var SQLiteAuthAdapter = class {
2005
+ db = null;
2006
+ path;
2007
+ saltRounds;
2008
+ externalDb;
2009
+ constructor(options = {}) {
2010
+ this.path = options.path || "./data.db";
2011
+ this.saltRounds = options.saltRounds || 12;
2012
+ this.externalDb = !!options.db;
2013
+ if (options.db) {
2014
+ this.db = options.db;
2015
+ }
2016
+ }
2017
+ async connect() {
2018
+ if (this.db) return;
2019
+ const Database = (await import('better-sqlite3')).default;
2020
+ this.db = new Database(this.path);
2021
+ this.db.pragma("journal_mode = WAL");
2022
+ this.db.pragma("foreign_keys = ON");
2023
+ this.ensureTables();
2024
+ }
2025
+ async disconnect() {
2026
+ if (this.db && !this.externalDb) {
2027
+ this.db.close();
2028
+ this.db = null;
2029
+ }
2030
+ }
2031
+ ensureTables() {
2032
+ if (!this.db) return;
2033
+ this.db.exec(`
2034
+ CREATE TABLE IF NOT EXISTS kyro_users (
2035
+ id TEXT PRIMARY KEY,
2036
+ email TEXT UNIQUE NOT NULL,
2037
+ password_hash TEXT NOT NULL,
2038
+ role TEXT NOT NULL DEFAULT 'customer',
2039
+ tenant_id TEXT,
2040
+ email_verified INTEGER DEFAULT 0,
2041
+ locked INTEGER DEFAULT 0,
2042
+ last_login TEXT,
2043
+ failed_login_attempts INTEGER DEFAULT 0,
2044
+ locked_until TEXT,
2045
+ created_at TEXT NOT NULL,
2046
+ updated_at TEXT NOT NULL
2047
+ );
2048
+
2049
+ CREATE TABLE IF NOT EXISTS kyro_sessions (
2050
+ id TEXT PRIMARY KEY,
2051
+ user_id TEXT NOT NULL,
2052
+ token TEXT NOT NULL,
2053
+ refresh_token TEXT,
2054
+ expires_at TEXT NOT NULL,
2055
+ created_at TEXT NOT NULL,
2056
+ ip_address TEXT,
2057
+ user_agent TEXT,
2058
+ FOREIGN KEY (user_id) REFERENCES kyro_users(id) ON DELETE CASCADE
2059
+ );
2060
+
2061
+ CREATE TABLE IF NOT EXISTS kyro_password_history (
2062
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2063
+ user_id TEXT NOT NULL,
2064
+ password_hash TEXT NOT NULL,
2065
+ created_at TEXT NOT NULL,
2066
+ FOREIGN KEY (user_id) REFERENCES kyro_users(id) ON DELETE CASCADE
2067
+ );
2068
+
2069
+ CREATE INDEX IF NOT EXISTS idx_kyro_users_email ON kyro_users(email);
2070
+ CREATE INDEX IF NOT EXISTS idx_kyro_sessions_user_id ON kyro_sessions(user_id);
2071
+ CREATE INDEX IF NOT EXISTS idx_kyro_sessions_token ON kyro_sessions(token);
2072
+ CREATE INDEX IF NOT EXISTS idx_kyro_sessions_refresh_token ON kyro_sessions(refresh_token);
2073
+ CREATE INDEX IF NOT EXISTS idx_kyro_password_history_user_id ON kyro_password_history(user_id);
2074
+ `);
2075
+ }
2076
+ async createUser(data) {
2077
+ if (!this.db) throw new Error("Not connected");
2078
+ const id = crypto.randomBytes(16).toString("hex");
2079
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2080
+ const user = {
2081
+ id,
2082
+ email: data.email.toLowerCase(),
2083
+ passwordHash: data.passwordHash,
2084
+ role: data.role || "customer",
2085
+ tenantId: data.tenantId,
2086
+ createdAt: now,
2087
+ updatedAt: now
2088
+ };
2089
+ this.db.prepare(
2090
+ `INSERT INTO kyro_users (id, email, password_hash, role, tenant_id, created_at, updated_at)
2091
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
2092
+ ).run(
2093
+ id,
2094
+ user.email,
2095
+ user.passwordHash,
2096
+ user.role,
2097
+ user.tenantId,
2098
+ now,
2099
+ now
2100
+ );
2101
+ return user;
2102
+ }
2103
+ async findUserByEmail(email) {
2104
+ if (!this.db) throw new Error("Not connected");
2105
+ const row = this.db.prepare("SELECT * FROM kyro_users WHERE email = ?").get(email.toLowerCase());
2106
+ if (!row) return null;
2107
+ return this.rowToUser(row);
2108
+ }
2109
+ async findUserById(userId) {
2110
+ if (!this.db) throw new Error("Not connected");
2111
+ const row = this.db.prepare("SELECT * FROM kyro_users WHERE id = ?").get(userId);
2112
+ if (!row) return null;
2113
+ return this.rowToUser(row);
2114
+ }
2115
+ async updateUser(userId, data) {
2116
+ if (!this.db) throw new Error("Not connected");
2117
+ const existing = await this.findUserById(userId);
2118
+ if (!existing) return null;
2119
+ const updates = [];
2120
+ const values = [];
2121
+ if (data.email !== void 0) {
2122
+ updates.push("email = ?");
2123
+ values.push(data.email.toLowerCase());
2124
+ }
2125
+ if (data.passwordHash !== void 0) {
2126
+ updates.push("password_hash = ?");
2127
+ values.push(data.passwordHash);
2128
+ }
2129
+ if (data.role !== void 0) {
2130
+ updates.push("role = ?");
2131
+ values.push(data.role);
2132
+ }
2133
+ if (data.tenantId !== void 0) {
2134
+ updates.push("tenant_id = ?");
2135
+ values.push(data.tenantId);
2136
+ }
2137
+ if (data.emailVerified !== void 0) {
2138
+ updates.push("email_verified = ?");
2139
+ values.push(data.emailVerified ? 1 : 0);
2140
+ }
2141
+ if (data.locked !== void 0) {
2142
+ updates.push("locked = ?");
2143
+ values.push(data.locked ? 1 : 0);
2144
+ }
2145
+ if (data.lastLogin !== void 0) {
2146
+ updates.push("last_login = ?");
2147
+ values.push(data.lastLogin);
2148
+ }
2149
+ if (data.failedLoginAttempts !== void 0) {
2150
+ updates.push("failed_login_attempts = ?");
2151
+ values.push(data.failedLoginAttempts);
2152
+ }
2153
+ updates.push("updated_at = ?");
2154
+ values.push((/* @__PURE__ */ new Date()).toISOString());
2155
+ values.push(userId);
2156
+ this.db.prepare(`UPDATE kyro_users SET ${updates.join(", ")} WHERE id = ?`).run(...values);
2157
+ return this.findUserById(userId);
2158
+ }
2159
+ async deleteUser(userId) {
2160
+ if (!this.db) throw new Error("Not connected");
2161
+ const result = this.db.prepare("DELETE FROM kyro_users WHERE id = ?").run(userId);
2162
+ return result.changes > 0;
2163
+ }
2164
+ async hashPassword(password) {
2165
+ return bcrypt__default.default.hash(password, this.saltRounds);
2166
+ }
2167
+ async verifyPassword(password, hash) {
2168
+ return bcrypt__default.default.compare(password, hash);
2169
+ }
2170
+ async createSession(userId, data = {}) {
2171
+ if (!this.db) throw new Error("Not connected");
2172
+ const id = crypto.randomBytes(32).toString("hex");
2173
+ const token = crypto.randomBytes(32).toString("base64url");
2174
+ const refreshToken = crypto.randomBytes(32).toString("base64url");
2175
+ const now = /* @__PURE__ */ new Date();
2176
+ const expiresAt = new Date(now.getTime() + 864e5).toISOString();
2177
+ const session = {
2178
+ id,
2179
+ userId,
2180
+ token,
2181
+ refreshToken,
2182
+ expiresAt,
2183
+ createdAt: now.toISOString(),
2184
+ ipAddress: data.ipAddress,
2185
+ userAgent: data.userAgent
2186
+ };
2187
+ this.db.prepare(
2188
+ `INSERT INTO kyro_sessions (id, user_id, token, refresh_token, expires_at, created_at, ip_address, user_agent)
2189
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
2190
+ ).run(
2191
+ session.id,
2192
+ session.userId,
2193
+ session.token,
2194
+ session.refreshToken,
2195
+ session.expiresAt,
2196
+ session.createdAt,
2197
+ session.ipAddress,
2198
+ session.userAgent
2199
+ );
2200
+ return session;
2201
+ }
2202
+ async findSessionByToken(token) {
2203
+ if (!this.db) throw new Error("Not connected");
2204
+ const row = this.db.prepare("SELECT * FROM kyro_sessions WHERE token = ?").get(token);
2205
+ if (!row) return null;
2206
+ return this.rowToSession(row);
2207
+ }
2208
+ async findSessionByRefreshToken(refreshToken) {
2209
+ if (!this.db) throw new Error("Not connected");
2210
+ const row = this.db.prepare("SELECT * FROM kyro_sessions WHERE refresh_token = ?").get(refreshToken);
2211
+ if (!row) return null;
2212
+ return this.rowToSession(row);
2213
+ }
2214
+ async deleteSession(sessionId) {
2215
+ if (!this.db) throw new Error("Not connected");
2216
+ const result = this.db.prepare("DELETE FROM kyro_sessions WHERE id = ? OR token = ?").run(sessionId, sessionId);
2217
+ return result.changes > 0;
2218
+ }
2219
+ async deleteUserSessions(userId) {
2220
+ if (!this.db) throw new Error("Not connected");
2221
+ const result = this.db.prepare("DELETE FROM kyro_sessions WHERE user_id = ?").run(userId);
2222
+ return result.changes;
2223
+ }
2224
+ async hasAnyUsers() {
2225
+ if (!this.db) throw new Error("Not connected");
2226
+ const row = this.db.prepare("SELECT COUNT(*) as count FROM kyro_users").get();
2227
+ return row.count > 0;
2228
+ }
2229
+ async addPasswordToHistory(userId, passwordHash) {
2230
+ if (!this.db) throw new Error("Not connected");
2231
+ this.db.prepare(
2232
+ "INSERT INTO kyro_password_history (user_id, password_hash, created_at) VALUES (?, ?, ?)"
2233
+ ).run(userId, passwordHash, (/* @__PURE__ */ new Date()).toISOString());
2234
+ this.db.prepare(
2235
+ `DELETE FROM kyro_password_history WHERE id IN (
2236
+ SELECT id FROM kyro_password_history WHERE user_id = ? ORDER BY created_at DESC LIMIT -1 OFFSET 5
2237
+ )`
2238
+ ).run(userId);
2239
+ }
2240
+ async getPasswordHistory(userId, count = 5) {
2241
+ if (!this.db) throw new Error("Not connected");
2242
+ const rows = this.db.prepare(
2243
+ "SELECT password_hash FROM kyro_password_history WHERE user_id = ? ORDER BY created_at DESC LIMIT ?"
2244
+ ).all(userId, count);
2245
+ return rows.map((r) => r.password_hash);
2246
+ }
2247
+ rowToUser(row) {
2248
+ return {
2249
+ id: row.id,
2250
+ email: row.email,
2251
+ passwordHash: row.password_hash,
2252
+ role: row.role,
2253
+ tenantId: row.tenant_id,
2254
+ emailVerified: row.email_verified === 1,
2255
+ locked: row.locked === 1,
2256
+ lastLogin: row.last_login,
2257
+ failedLoginAttempts: row.failed_login_attempts || 0,
2258
+ createdAt: row.created_at,
2259
+ updatedAt: row.updated_at
2260
+ };
2261
+ }
2262
+ rowToSession(row) {
2263
+ return {
2264
+ id: row.id,
2265
+ userId: row.user_id,
2266
+ token: row.token,
2267
+ refreshToken: row.refresh_token,
2268
+ expiresAt: row.expires_at,
2269
+ createdAt: row.created_at,
2270
+ ipAddress: row.ip_address,
2271
+ userAgent: row.user_agent
2272
+ };
2273
+ }
2274
+ };
2002
2275
 
2003
2276
  // src/auth/security/lockout.ts
2004
2277
  var DEFAULT_LOCKOUT_CONFIG = {
@@ -3218,7 +3491,7 @@ var Auth = class {
3218
3491
  return jwt2__default.default.sign(payload, this.config.secret, signOptions);
3219
3492
  }
3220
3493
  async hashPassword(password) {
3221
- return bcrypt__default.default.hash(password, this.config.saltRounds);
3494
+ return bcrypt2__default.default.hash(password, this.config.saltRounds);
3222
3495
  }
3223
3496
  parseExpiresIn(value) {
3224
3497
  if (typeof value === "number") return value;
@@ -3635,6 +3908,7 @@ exports.RateLimiter = RateLimiter;
3635
3908
  exports.Registry = Registry;
3636
3909
  exports.ReviewsPlugin = ReviewsPlugin;
3637
3910
  exports.SEOPLugin = SEOPLugin;
3911
+ exports.SQLiteAuthAdapter = SQLiteAuthAdapter;
3638
3912
  exports.VersionManager = VersionManager;
3639
3913
  exports.WishlistPlugin = WishlistPlugin;
3640
3914
  exports.authConfig = authConfig;