@kyro-cms/core 0.1.2 → 0.1.3
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 +12 -10
- package/dist/index.cjs +277 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +275 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
- **
|
|
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 (
|
|
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
|
|
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
|
|
302
|
-
const adapter = new
|
|
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
|
-
- **
|
|
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
|
|
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
|
|
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
|
|
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;
|