@objectstack/plugin-auth 2.0.2 → 2.0.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/.turbo/turbo-build.log +10 -10
- package/ARCHITECTURE.md +176 -0
- package/CHANGELOG.md +19 -0
- package/IMPLEMENTATION_SUMMARY.md +69 -27
- package/README.md +153 -25
- package/dist/index.d.mts +7485 -5
- package/dist/index.d.ts +7485 -5
- package/dist/index.js +572 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +565 -75
- package/dist/index.mjs.map +1 -1
- package/examples/basic-usage.ts +36 -24
- package/package.json +4 -11
- package/src/auth-manager.ts +164 -0
- package/src/auth-plugin.test.ts +14 -4
- package/src/auth-plugin.ts +89 -99
- package/src/index.ts +5 -1
- package/src/objectql-adapter.ts +181 -0
- package/src/objects/auth-account.object.ts +121 -0
- package/src/objects/auth-session.object.ts +89 -0
- package/src/objects/auth-user.object.ts +97 -0
- package/src/objects/auth-verification.object.ts +78 -0
- package/src/objects/index.ts +13 -0
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,210 @@
|
|
|
1
|
+
// src/auth-manager.ts
|
|
2
|
+
import { betterAuth } from "better-auth";
|
|
3
|
+
|
|
4
|
+
// src/objectql-adapter.ts
|
|
5
|
+
function createObjectQLAdapter(dataEngine) {
|
|
6
|
+
function convertWhere(where) {
|
|
7
|
+
const filter = {};
|
|
8
|
+
for (const condition of where) {
|
|
9
|
+
const fieldName = condition.field;
|
|
10
|
+
if (condition.operator === "eq") {
|
|
11
|
+
filter[fieldName] = condition.value;
|
|
12
|
+
} else if (condition.operator === "ne") {
|
|
13
|
+
filter[fieldName] = { $ne: condition.value };
|
|
14
|
+
} else if (condition.operator === "in") {
|
|
15
|
+
filter[fieldName] = { $in: condition.value };
|
|
16
|
+
} else if (condition.operator === "gt") {
|
|
17
|
+
filter[fieldName] = { $gt: condition.value };
|
|
18
|
+
} else if (condition.operator === "gte") {
|
|
19
|
+
filter[fieldName] = { $gte: condition.value };
|
|
20
|
+
} else if (condition.operator === "lt") {
|
|
21
|
+
filter[fieldName] = { $lt: condition.value };
|
|
22
|
+
} else if (condition.operator === "lte") {
|
|
23
|
+
filter[fieldName] = { $lte: condition.value };
|
|
24
|
+
} else if (condition.operator === "contains") {
|
|
25
|
+
filter[fieldName] = { $regex: condition.value };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return filter;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
create: async ({ model, data, select: _select }) => {
|
|
32
|
+
const objectName = model;
|
|
33
|
+
const result = await dataEngine.insert(objectName, data);
|
|
34
|
+
return result;
|
|
35
|
+
},
|
|
36
|
+
findOne: async ({ model, where, select, join: _join }) => {
|
|
37
|
+
const objectName = model;
|
|
38
|
+
const filter = convertWhere(where);
|
|
39
|
+
const result = await dataEngine.findOne(objectName, {
|
|
40
|
+
filter,
|
|
41
|
+
select
|
|
42
|
+
});
|
|
43
|
+
return result ? result : null;
|
|
44
|
+
},
|
|
45
|
+
findMany: async ({ model, where, limit, offset, sortBy, join: _join }) => {
|
|
46
|
+
const objectName = model;
|
|
47
|
+
const filter = where ? convertWhere(where) : {};
|
|
48
|
+
const sort = sortBy ? [{
|
|
49
|
+
field: sortBy.field,
|
|
50
|
+
order: sortBy.direction
|
|
51
|
+
}] : void 0;
|
|
52
|
+
const results = await dataEngine.find(objectName, {
|
|
53
|
+
filter,
|
|
54
|
+
limit: limit || 100,
|
|
55
|
+
skip: offset,
|
|
56
|
+
sort
|
|
57
|
+
});
|
|
58
|
+
return results;
|
|
59
|
+
},
|
|
60
|
+
count: async ({ model, where }) => {
|
|
61
|
+
const objectName = model;
|
|
62
|
+
const filter = where ? convertWhere(where) : {};
|
|
63
|
+
return await dataEngine.count(objectName, { filter });
|
|
64
|
+
},
|
|
65
|
+
update: async ({ model, where, update }) => {
|
|
66
|
+
const objectName = model;
|
|
67
|
+
const filter = convertWhere(where);
|
|
68
|
+
const record = await dataEngine.findOne(objectName, { filter });
|
|
69
|
+
if (!record) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const result = await dataEngine.update(objectName, {
|
|
73
|
+
...update,
|
|
74
|
+
id: record.id
|
|
75
|
+
});
|
|
76
|
+
return result ? result : null;
|
|
77
|
+
},
|
|
78
|
+
updateMany: async ({ model, where, update }) => {
|
|
79
|
+
const objectName = model;
|
|
80
|
+
const filter = convertWhere(where);
|
|
81
|
+
const records = await dataEngine.find(objectName, { filter });
|
|
82
|
+
for (const record of records) {
|
|
83
|
+
await dataEngine.update(objectName, {
|
|
84
|
+
...update,
|
|
85
|
+
id: record.id
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return records.length;
|
|
89
|
+
},
|
|
90
|
+
delete: async ({ model, where }) => {
|
|
91
|
+
const objectName = model;
|
|
92
|
+
const filter = convertWhere(where);
|
|
93
|
+
const record = await dataEngine.findOne(objectName, { filter });
|
|
94
|
+
if (!record) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
await dataEngine.delete(objectName, { filter: { id: record.id } });
|
|
98
|
+
},
|
|
99
|
+
deleteMany: async ({ model, where }) => {
|
|
100
|
+
const objectName = model;
|
|
101
|
+
const filter = convertWhere(where);
|
|
102
|
+
const records = await dataEngine.find(objectName, { filter });
|
|
103
|
+
for (const record of records) {
|
|
104
|
+
await dataEngine.delete(objectName, { filter: { id: record.id } });
|
|
105
|
+
}
|
|
106
|
+
return records.length;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/auth-manager.ts
|
|
112
|
+
var AuthManager = class {
|
|
113
|
+
constructor(config) {
|
|
114
|
+
this.auth = null;
|
|
115
|
+
this.config = config;
|
|
116
|
+
if (config.authInstance) {
|
|
117
|
+
this.auth = config.authInstance;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get or create the better-auth instance (lazy initialization)
|
|
122
|
+
*/
|
|
123
|
+
getOrCreateAuth() {
|
|
124
|
+
if (!this.auth) {
|
|
125
|
+
this.auth = this.createAuthInstance();
|
|
126
|
+
}
|
|
127
|
+
return this.auth;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create a better-auth instance from configuration
|
|
131
|
+
*/
|
|
132
|
+
createAuthInstance() {
|
|
133
|
+
const betterAuthConfig = {
|
|
134
|
+
// Base configuration
|
|
135
|
+
secret: this.config.secret || this.generateSecret(),
|
|
136
|
+
baseURL: this.config.baseUrl || "http://localhost:3000",
|
|
137
|
+
// Database adapter configuration
|
|
138
|
+
// For now, we configure a basic setup that will be enhanced
|
|
139
|
+
// when database URL is provided and drizzle-orm is available
|
|
140
|
+
database: this.createDatabaseConfig(),
|
|
141
|
+
// Email configuration
|
|
142
|
+
emailAndPassword: {
|
|
143
|
+
enabled: true
|
|
144
|
+
},
|
|
145
|
+
// Session configuration
|
|
146
|
+
session: {
|
|
147
|
+
expiresIn: this.config.session?.expiresIn || 60 * 60 * 24 * 7,
|
|
148
|
+
// 7 days default
|
|
149
|
+
updateAge: this.config.session?.updateAge || 60 * 60 * 24
|
|
150
|
+
// 1 day default
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
return betterAuth(betterAuthConfig);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Create database configuration using ObjectQL adapter
|
|
157
|
+
*/
|
|
158
|
+
createDatabaseConfig() {
|
|
159
|
+
if (this.config.dataEngine) {
|
|
160
|
+
return createObjectQLAdapter(this.config.dataEngine);
|
|
161
|
+
}
|
|
162
|
+
console.warn(
|
|
163
|
+
"\u26A0\uFE0F WARNING: No dataEngine provided to AuthManager! Using in-memory storage. This is NOT suitable for production. Please provide a dataEngine instance (e.g., ObjectQL) in AuthManagerOptions."
|
|
164
|
+
);
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Generate a secure secret if not provided
|
|
169
|
+
*/
|
|
170
|
+
generateSecret() {
|
|
171
|
+
const envSecret = process.env.AUTH_SECRET;
|
|
172
|
+
if (!envSecret) {
|
|
173
|
+
const fallbackSecret = "dev-secret-" + Date.now();
|
|
174
|
+
console.warn(
|
|
175
|
+
"\u26A0\uFE0F WARNING: No AUTH_SECRET environment variable set! Using a temporary development secret. This is NOT secure for production use. Please set AUTH_SECRET in your environment variables."
|
|
176
|
+
);
|
|
177
|
+
return fallbackSecret;
|
|
178
|
+
}
|
|
179
|
+
return envSecret;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the underlying better-auth instance
|
|
183
|
+
* Useful for advanced use cases
|
|
184
|
+
*/
|
|
185
|
+
getAuthInstance() {
|
|
186
|
+
return this.getOrCreateAuth();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Handle an authentication request
|
|
190
|
+
* Forwards the request directly to better-auth's universal handler
|
|
191
|
+
*
|
|
192
|
+
* @param request - Web standard Request object
|
|
193
|
+
* @returns Web standard Response object
|
|
194
|
+
*/
|
|
195
|
+
async handleRequest(request) {
|
|
196
|
+
const auth = this.getOrCreateAuth();
|
|
197
|
+
return await auth.handler(request);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get the better-auth API for programmatic access
|
|
201
|
+
* Use this for server-side operations (e.g., creating users, checking sessions)
|
|
202
|
+
*/
|
|
203
|
+
get api() {
|
|
204
|
+
return this.getOrCreateAuth().api;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
1
208
|
// src/auth-plugin.ts
|
|
2
209
|
var AuthPlugin = class {
|
|
3
210
|
constructor(options = {}) {
|
|
@@ -17,7 +224,14 @@ var AuthPlugin = class {
|
|
|
17
224
|
if (!this.options.secret) {
|
|
18
225
|
throw new Error("AuthPlugin: secret is required");
|
|
19
226
|
}
|
|
20
|
-
|
|
227
|
+
const dataEngine = ctx.getService("data");
|
|
228
|
+
if (!dataEngine) {
|
|
229
|
+
ctx.logger.warn("No data engine service found - auth will use in-memory storage");
|
|
230
|
+
}
|
|
231
|
+
this.authManager = new AuthManager({
|
|
232
|
+
...this.options,
|
|
233
|
+
dataEngine
|
|
234
|
+
});
|
|
21
235
|
ctx.registerService("auth", this.authManager);
|
|
22
236
|
ctx.logger.info("Auth Plugin initialized successfully");
|
|
23
237
|
}
|
|
@@ -37,6 +251,20 @@ var AuthPlugin = class {
|
|
|
37
251
|
throw err;
|
|
38
252
|
}
|
|
39
253
|
}
|
|
254
|
+
try {
|
|
255
|
+
const ql = ctx.getService("objectql");
|
|
256
|
+
if (ql && typeof ql.registerMiddleware === "function") {
|
|
257
|
+
ql.registerMiddleware(async (opCtx, next) => {
|
|
258
|
+
if (opCtx.context?.userId || opCtx.context?.isSystem) {
|
|
259
|
+
return next();
|
|
260
|
+
}
|
|
261
|
+
await next();
|
|
262
|
+
});
|
|
263
|
+
ctx.logger.info("Auth middleware registered on ObjectQL engine");
|
|
264
|
+
}
|
|
265
|
+
} catch (_e) {
|
|
266
|
+
ctx.logger.debug("ObjectQL engine not available, skipping auth middleware registration");
|
|
267
|
+
}
|
|
40
268
|
ctx.logger.info("Auth Plugin started successfully");
|
|
41
269
|
}
|
|
42
270
|
async destroy() {
|
|
@@ -44,95 +272,357 @@ var AuthPlugin = class {
|
|
|
44
272
|
}
|
|
45
273
|
/**
|
|
46
274
|
* Register authentication routes with HTTP server
|
|
275
|
+
*
|
|
276
|
+
* Uses better-auth's universal handler for all authentication requests.
|
|
277
|
+
* This forwards all requests under basePath to better-auth, which handles:
|
|
278
|
+
* - Email/password authentication
|
|
279
|
+
* - OAuth providers (Google, GitHub, etc.)
|
|
280
|
+
* - Session management
|
|
281
|
+
* - Password reset
|
|
282
|
+
* - Email verification
|
|
283
|
+
* - 2FA, passkeys, magic links (if enabled)
|
|
47
284
|
*/
|
|
48
285
|
registerAuthRoutes(httpServer, ctx) {
|
|
49
286
|
if (!this.authManager) return;
|
|
50
287
|
const basePath = this.options.basePath || "/api/v1/auth";
|
|
51
|
-
httpServer.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
res.status(401).json({
|
|
60
|
-
success: false,
|
|
61
|
-
error: err.message
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
httpServer.post(`${basePath}/register`, async (req, res) => {
|
|
66
|
-
try {
|
|
67
|
-
const body = req.body;
|
|
68
|
-
const result = await this.authManager.register(body);
|
|
69
|
-
res.status(201).json(result);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
72
|
-
ctx.logger.error("Registration error:", err);
|
|
73
|
-
res.status(400).json({
|
|
74
|
-
success: false,
|
|
75
|
-
error: err.message
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
httpServer.post(`${basePath}/logout`, async (req, res) => {
|
|
288
|
+
if (!("getRawApp" in httpServer) || typeof httpServer.getRawApp !== "function") {
|
|
289
|
+
ctx.logger.error("HTTP server does not support getRawApp() - wildcard routing requires Hono server");
|
|
290
|
+
throw new Error(
|
|
291
|
+
"AuthPlugin requires HonoServerPlugin for wildcard routing support. Please ensure HonoServerPlugin is loaded before AuthPlugin."
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
const rawApp = httpServer.getRawApp();
|
|
295
|
+
rawApp.all(`${basePath}/*`, async (c) => {
|
|
80
296
|
try {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
297
|
+
const request = c.req.raw;
|
|
298
|
+
const url = new URL(request.url);
|
|
299
|
+
const authPath = url.pathname.replace(basePath, "");
|
|
300
|
+
const rewrittenUrl = new URL(authPath || "/", url.origin);
|
|
301
|
+
rewrittenUrl.search = url.search;
|
|
302
|
+
const rewrittenRequest = new Request(rewrittenUrl, {
|
|
303
|
+
method: request.method,
|
|
304
|
+
headers: request.headers,
|
|
305
|
+
body: request.body,
|
|
306
|
+
duplex: "half"
|
|
307
|
+
// Required for Request with body
|
|
91
308
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
httpServer.get(`${basePath}/session`, async (req, res) => {
|
|
95
|
-
try {
|
|
96
|
-
const authHeader = req.headers["authorization"];
|
|
97
|
-
const token = typeof authHeader === "string" ? authHeader.replace("Bearer ", "") : void 0;
|
|
98
|
-
const session = await this.authManager.getSession(token);
|
|
99
|
-
res.status(200).json({ success: true, data: session });
|
|
309
|
+
const response = await this.authManager.handleRequest(rewrittenRequest);
|
|
310
|
+
return response;
|
|
100
311
|
} catch (error) {
|
|
101
312
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
313
|
+
ctx.logger.error("Auth request error:", err);
|
|
314
|
+
return new Response(
|
|
315
|
+
JSON.stringify({
|
|
316
|
+
success: false,
|
|
317
|
+
error: err.message
|
|
318
|
+
}),
|
|
319
|
+
{
|
|
320
|
+
status: 500,
|
|
321
|
+
headers: { "Content-Type": "application/json" }
|
|
322
|
+
}
|
|
323
|
+
);
|
|
106
324
|
}
|
|
107
325
|
});
|
|
108
|
-
ctx.logger.
|
|
109
|
-
basePath,
|
|
110
|
-
routes: [
|
|
111
|
-
`POST ${basePath}/login`,
|
|
112
|
-
`POST ${basePath}/register`,
|
|
113
|
-
`POST ${basePath}/logout`,
|
|
114
|
-
`GET ${basePath}/session`
|
|
115
|
-
]
|
|
116
|
-
});
|
|
326
|
+
ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
|
|
117
327
|
}
|
|
118
328
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
329
|
+
|
|
330
|
+
// src/objects/auth-user.object.ts
|
|
331
|
+
import { ObjectSchema, Field } from "@objectstack/spec/data";
|
|
332
|
+
var AuthUser = ObjectSchema.create({
|
|
333
|
+
name: "user",
|
|
334
|
+
label: "User",
|
|
335
|
+
pluralLabel: "Users",
|
|
336
|
+
icon: "user",
|
|
337
|
+
description: "User accounts for authentication",
|
|
338
|
+
titleFormat: "{name} ({email})",
|
|
339
|
+
compactLayout: ["name", "email", "emailVerified"],
|
|
340
|
+
fields: {
|
|
341
|
+
// ID is auto-generated by ObjectQL
|
|
342
|
+
id: Field.text({
|
|
343
|
+
label: "User ID",
|
|
344
|
+
required: true,
|
|
345
|
+
readonly: true
|
|
346
|
+
}),
|
|
347
|
+
createdAt: Field.datetime({
|
|
348
|
+
label: "Created At",
|
|
349
|
+
defaultValue: "NOW()",
|
|
350
|
+
readonly: true
|
|
351
|
+
}),
|
|
352
|
+
updatedAt: Field.datetime({
|
|
353
|
+
label: "Updated At",
|
|
354
|
+
defaultValue: "NOW()",
|
|
355
|
+
readonly: true
|
|
356
|
+
}),
|
|
357
|
+
email: Field.email({
|
|
358
|
+
label: "Email",
|
|
359
|
+
required: true,
|
|
360
|
+
searchable: true
|
|
361
|
+
}),
|
|
362
|
+
emailVerified: Field.boolean({
|
|
363
|
+
label: "Email Verified",
|
|
364
|
+
defaultValue: false
|
|
365
|
+
}),
|
|
366
|
+
name: Field.text({
|
|
367
|
+
label: "Name",
|
|
368
|
+
required: true,
|
|
369
|
+
searchable: true,
|
|
370
|
+
maxLength: 255
|
|
371
|
+
}),
|
|
372
|
+
image: Field.url({
|
|
373
|
+
label: "Profile Image",
|
|
374
|
+
required: false
|
|
375
|
+
})
|
|
376
|
+
},
|
|
377
|
+
// Database indexes for performance
|
|
378
|
+
indexes: [
|
|
379
|
+
{ fields: ["email"], unique: true },
|
|
380
|
+
{ fields: ["createdAt"], unique: false }
|
|
381
|
+
],
|
|
382
|
+
// Enable features
|
|
383
|
+
enable: {
|
|
384
|
+
trackHistory: true,
|
|
385
|
+
searchable: true,
|
|
386
|
+
apiEnabled: true,
|
|
387
|
+
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
388
|
+
trash: true,
|
|
389
|
+
mru: true
|
|
390
|
+
},
|
|
391
|
+
// Validation Rules
|
|
392
|
+
validations: [
|
|
393
|
+
{
|
|
394
|
+
name: "email_unique",
|
|
395
|
+
type: "unique",
|
|
396
|
+
severity: "error",
|
|
397
|
+
message: "Email must be unique",
|
|
398
|
+
fields: ["email"],
|
|
399
|
+
caseSensitive: false
|
|
400
|
+
}
|
|
401
|
+
]
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// src/objects/auth-session.object.ts
|
|
405
|
+
import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
|
|
406
|
+
var AuthSession = ObjectSchema2.create({
|
|
407
|
+
name: "session",
|
|
408
|
+
label: "Session",
|
|
409
|
+
pluralLabel: "Sessions",
|
|
410
|
+
icon: "key",
|
|
411
|
+
description: "Active user sessions",
|
|
412
|
+
titleFormat: "Session {token}",
|
|
413
|
+
compactLayout: ["userId", "expiresAt", "ipAddress"],
|
|
414
|
+
fields: {
|
|
415
|
+
id: Field2.text({
|
|
416
|
+
label: "Session ID",
|
|
417
|
+
required: true,
|
|
418
|
+
readonly: true
|
|
419
|
+
}),
|
|
420
|
+
createdAt: Field2.datetime({
|
|
421
|
+
label: "Created At",
|
|
422
|
+
defaultValue: "NOW()",
|
|
423
|
+
readonly: true
|
|
424
|
+
}),
|
|
425
|
+
updatedAt: Field2.datetime({
|
|
426
|
+
label: "Updated At",
|
|
427
|
+
defaultValue: "NOW()",
|
|
428
|
+
readonly: true
|
|
429
|
+
}),
|
|
430
|
+
userId: Field2.text({
|
|
431
|
+
label: "User ID",
|
|
432
|
+
required: true
|
|
433
|
+
}),
|
|
434
|
+
expiresAt: Field2.datetime({
|
|
435
|
+
label: "Expires At",
|
|
436
|
+
required: true
|
|
437
|
+
}),
|
|
438
|
+
token: Field2.text({
|
|
439
|
+
label: "Session Token",
|
|
440
|
+
required: true
|
|
441
|
+
}),
|
|
442
|
+
ipAddress: Field2.text({
|
|
443
|
+
label: "IP Address",
|
|
444
|
+
required: false,
|
|
445
|
+
maxLength: 45
|
|
446
|
+
// Support IPv6
|
|
447
|
+
}),
|
|
448
|
+
userAgent: Field2.textarea({
|
|
449
|
+
label: "User Agent",
|
|
450
|
+
required: false
|
|
451
|
+
})
|
|
452
|
+
},
|
|
453
|
+
// Database indexes for performance
|
|
454
|
+
indexes: [
|
|
455
|
+
{ fields: ["token"], unique: true },
|
|
456
|
+
{ fields: ["userId"], unique: false },
|
|
457
|
+
{ fields: ["expiresAt"], unique: false }
|
|
458
|
+
],
|
|
459
|
+
// Enable features
|
|
460
|
+
enable: {
|
|
461
|
+
trackHistory: false,
|
|
462
|
+
// Sessions don't need history tracking
|
|
463
|
+
searchable: false,
|
|
464
|
+
apiEnabled: true,
|
|
465
|
+
apiMethods: ["get", "list", "create", "delete"],
|
|
466
|
+
// No update for sessions
|
|
467
|
+
trash: false,
|
|
468
|
+
// Sessions should be hard deleted
|
|
469
|
+
mru: false
|
|
127
470
|
}
|
|
128
|
-
|
|
129
|
-
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// src/objects/auth-account.object.ts
|
|
474
|
+
import { ObjectSchema as ObjectSchema3, Field as Field3 } from "@objectstack/spec/data";
|
|
475
|
+
var AuthAccount = ObjectSchema3.create({
|
|
476
|
+
name: "account",
|
|
477
|
+
label: "Account",
|
|
478
|
+
pluralLabel: "Accounts",
|
|
479
|
+
icon: "link",
|
|
480
|
+
description: "OAuth and authentication provider accounts",
|
|
481
|
+
titleFormat: "{providerId} - {accountId}",
|
|
482
|
+
compactLayout: ["providerId", "userId", "accountId"],
|
|
483
|
+
fields: {
|
|
484
|
+
id: Field3.text({
|
|
485
|
+
label: "Account ID",
|
|
486
|
+
required: true,
|
|
487
|
+
readonly: true
|
|
488
|
+
}),
|
|
489
|
+
createdAt: Field3.datetime({
|
|
490
|
+
label: "Created At",
|
|
491
|
+
defaultValue: "NOW()",
|
|
492
|
+
readonly: true
|
|
493
|
+
}),
|
|
494
|
+
updatedAt: Field3.datetime({
|
|
495
|
+
label: "Updated At",
|
|
496
|
+
defaultValue: "NOW()",
|
|
497
|
+
readonly: true
|
|
498
|
+
}),
|
|
499
|
+
providerId: Field3.text({
|
|
500
|
+
label: "Provider ID",
|
|
501
|
+
required: true,
|
|
502
|
+
description: "OAuth provider identifier (google, github, etc.)"
|
|
503
|
+
}),
|
|
504
|
+
accountId: Field3.text({
|
|
505
|
+
label: "Provider Account ID",
|
|
506
|
+
required: true,
|
|
507
|
+
description: "User's ID in the provider's system"
|
|
508
|
+
}),
|
|
509
|
+
userId: Field3.text({
|
|
510
|
+
label: "User ID",
|
|
511
|
+
required: true,
|
|
512
|
+
description: "Link to user table"
|
|
513
|
+
}),
|
|
514
|
+
accessToken: Field3.textarea({
|
|
515
|
+
label: "Access Token",
|
|
516
|
+
required: false
|
|
517
|
+
}),
|
|
518
|
+
refreshToken: Field3.textarea({
|
|
519
|
+
label: "Refresh Token",
|
|
520
|
+
required: false
|
|
521
|
+
}),
|
|
522
|
+
idToken: Field3.textarea({
|
|
523
|
+
label: "ID Token",
|
|
524
|
+
required: false
|
|
525
|
+
}),
|
|
526
|
+
accessTokenExpiresAt: Field3.datetime({
|
|
527
|
+
label: "Access Token Expires At",
|
|
528
|
+
required: false
|
|
529
|
+
}),
|
|
530
|
+
refreshTokenExpiresAt: Field3.datetime({
|
|
531
|
+
label: "Refresh Token Expires At",
|
|
532
|
+
required: false
|
|
533
|
+
}),
|
|
534
|
+
scope: Field3.text({
|
|
535
|
+
label: "OAuth Scope",
|
|
536
|
+
required: false
|
|
537
|
+
}),
|
|
538
|
+
password: Field3.text({
|
|
539
|
+
label: "Password Hash",
|
|
540
|
+
required: false,
|
|
541
|
+
description: "Hashed password for email/password provider"
|
|
542
|
+
})
|
|
543
|
+
},
|
|
544
|
+
// Database indexes for performance
|
|
545
|
+
indexes: [
|
|
546
|
+
{ fields: ["userId"], unique: false },
|
|
547
|
+
{ fields: ["providerId", "accountId"], unique: true }
|
|
548
|
+
],
|
|
549
|
+
// Enable features
|
|
550
|
+
enable: {
|
|
551
|
+
trackHistory: false,
|
|
552
|
+
searchable: false,
|
|
553
|
+
apiEnabled: true,
|
|
554
|
+
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
555
|
+
trash: true,
|
|
556
|
+
mru: false
|
|
130
557
|
}
|
|
131
|
-
|
|
132
|
-
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// src/objects/auth-verification.object.ts
|
|
561
|
+
import { ObjectSchema as ObjectSchema4, Field as Field4 } from "@objectstack/spec/data";
|
|
562
|
+
var AuthVerification = ObjectSchema4.create({
|
|
563
|
+
name: "verification",
|
|
564
|
+
label: "Verification",
|
|
565
|
+
pluralLabel: "Verifications",
|
|
566
|
+
icon: "shield-check",
|
|
567
|
+
description: "Email and phone verification tokens",
|
|
568
|
+
titleFormat: "Verification for {identifier}",
|
|
569
|
+
compactLayout: ["identifier", "expiresAt", "createdAt"],
|
|
570
|
+
fields: {
|
|
571
|
+
id: Field4.text({
|
|
572
|
+
label: "Verification ID",
|
|
573
|
+
required: true,
|
|
574
|
+
readonly: true
|
|
575
|
+
}),
|
|
576
|
+
createdAt: Field4.datetime({
|
|
577
|
+
label: "Created At",
|
|
578
|
+
defaultValue: "NOW()",
|
|
579
|
+
readonly: true
|
|
580
|
+
}),
|
|
581
|
+
updatedAt: Field4.datetime({
|
|
582
|
+
label: "Updated At",
|
|
583
|
+
defaultValue: "NOW()",
|
|
584
|
+
readonly: true
|
|
585
|
+
}),
|
|
586
|
+
value: Field4.text({
|
|
587
|
+
label: "Verification Token",
|
|
588
|
+
required: true,
|
|
589
|
+
description: "Token or code for verification"
|
|
590
|
+
}),
|
|
591
|
+
expiresAt: Field4.datetime({
|
|
592
|
+
label: "Expires At",
|
|
593
|
+
required: true
|
|
594
|
+
}),
|
|
595
|
+
identifier: Field4.text({
|
|
596
|
+
label: "Identifier",
|
|
597
|
+
required: true,
|
|
598
|
+
description: "Email address or phone number"
|
|
599
|
+
})
|
|
600
|
+
},
|
|
601
|
+
// Database indexes for performance
|
|
602
|
+
indexes: [
|
|
603
|
+
{ fields: ["value"], unique: true },
|
|
604
|
+
{ fields: ["identifier"], unique: false },
|
|
605
|
+
{ fields: ["expiresAt"], unique: false }
|
|
606
|
+
],
|
|
607
|
+
// Enable features
|
|
608
|
+
enable: {
|
|
609
|
+
trackHistory: false,
|
|
610
|
+
searchable: false,
|
|
611
|
+
apiEnabled: true,
|
|
612
|
+
apiMethods: ["get", "create", "delete"],
|
|
613
|
+
// No list or update
|
|
614
|
+
trash: false,
|
|
615
|
+
// Hard delete expired tokens
|
|
616
|
+
mru: false
|
|
133
617
|
}
|
|
134
|
-
};
|
|
618
|
+
});
|
|
135
619
|
export {
|
|
136
|
-
|
|
620
|
+
AuthAccount,
|
|
621
|
+
AuthManager,
|
|
622
|
+
AuthPlugin,
|
|
623
|
+
AuthSession,
|
|
624
|
+
AuthUser,
|
|
625
|
+
AuthVerification,
|
|
626
|
+
createObjectQLAdapter
|
|
137
627
|
};
|
|
138
628
|
//# sourceMappingURL=index.mjs.map
|