@happyvertical/smrt-profiles 0.30.0

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.
Files changed (42) hide show
  1. package/AGENTS.md +53 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +176 -0
  5. package/dist/chunks/ApiKey-B2LKEaP8.js +143 -0
  6. package/dist/chunks/ApiKey-B2LKEaP8.js.map +1 -0
  7. package/dist/chunks/ApiKeyCollection-B6Op817e.js +91 -0
  8. package/dist/chunks/ApiKeyCollection-B6Op817e.js.map +1 -0
  9. package/dist/chunks/AuditLogCollection-BYqCj0uE.js +195 -0
  10. package/dist/chunks/AuditLogCollection-BYqCj0uE.js.map +1 -0
  11. package/dist/chunks/NostrIdentityCollection-DadQBHWy.js +3065 -0
  12. package/dist/chunks/NostrIdentityCollection-DadQBHWy.js.map +1 -0
  13. package/dist/chunks/ProfileAssetCollection-D_tk1kKG.js +122 -0
  14. package/dist/chunks/ProfileAssetCollection-D_tk1kKG.js.map +1 -0
  15. package/dist/chunks/ProfileCollection-DU6wUJTO.js +782 -0
  16. package/dist/chunks/ProfileCollection-DU6wUJTO.js.map +1 -0
  17. package/dist/chunks/ProfileMetadataCollection-DEhmljMY.js +120 -0
  18. package/dist/chunks/ProfileMetadataCollection-DEhmljMY.js.map +1 -0
  19. package/dist/chunks/ProfileMetafieldCollection-DMKhSHXX.js +184 -0
  20. package/dist/chunks/ProfileMetafieldCollection-DMKhSHXX.js.map +1 -0
  21. package/dist/chunks/ProfileRelationshipCollection-C0IM8UQR.js +177 -0
  22. package/dist/chunks/ProfileRelationshipCollection-C0IM8UQR.js.map +1 -0
  23. package/dist/chunks/ProfileRelationshipTermCollection-CXem_qT-.js +117 -0
  24. package/dist/chunks/ProfileRelationshipTermCollection-CXem_qT-.js.map +1 -0
  25. package/dist/chunks/ProfileRelationshipType-BXBLldea.js +103 -0
  26. package/dist/chunks/ProfileRelationshipType-BXBLldea.js.map +1 -0
  27. package/dist/chunks/ProfileRelationshipTypeCollection-CF8YvLTV.js +48 -0
  28. package/dist/chunks/ProfileRelationshipTypeCollection-CF8YvLTV.js.map +1 -0
  29. package/dist/chunks/index-jFtOWsAV.js +1014 -0
  30. package/dist/chunks/index-jFtOWsAV.js.map +1 -0
  31. package/dist/index.d.ts +1848 -0
  32. package/dist/index.js +70 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/manifest.json +11829 -0
  35. package/dist/smrt-knowledge.json +3846 -0
  36. package/dist/types.d.ts +41 -0
  37. package/dist/types.js +2 -0
  38. package/dist/types.js.map +1 -0
  39. package/dist/utils.d.ts +61 -0
  40. package/dist/utils.js +49 -0
  41. package/dist/utils.js.map +1 -0
  42. package/package.json +75 -0
@@ -0,0 +1,1014 @@
1
+ import { ObjectRegistry, foreignKey, smrt, SmrtObject, SmrtCollection, field } from "@happyvertical/smrt-core";
2
+ import { P as ProfileType, a as Profile } from "./ProfileCollection-DU6wUJTO.js";
3
+ import { createHash, randomBytes } from "node:crypto";
4
+ import { N as NostrIdentityCollection, g as generateNostrKeypair, e as encryptPrivkey, a as NostrIdentity, v as verifyAuthEvent } from "./NostrIdentityCollection-DadQBHWy.js";
5
+ import { ApiKey } from "./ApiKey-B2LKEaP8.js";
6
+ import "./ApiKeyCollection-B6Op817e.js";
7
+ import "./AuditLogCollection-BYqCj0uE.js";
8
+ import "./ProfileAssetCollection-D_tk1kKG.js";
9
+ import "./ProfileMetadataCollection-DEhmljMY.js";
10
+ import "./ProfileMetafieldCollection-DMKhSHXX.js";
11
+ import "./ProfileRelationshipCollection-C0IM8UQR.js";
12
+ import "./ProfileRelationshipTermCollection-CXem_qT-.js";
13
+ import "./ProfileRelationshipTypeCollection-CF8YvLTV.js";
14
+ import "./ProfileRelationshipType-BXBLldea.js";
15
+ ObjectRegistry.registerPackageManifest(
16
+ new URL("./manifest.json", import.meta.url)
17
+ );
18
+ var __defProp$1 = Object.defineProperty;
19
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
20
+ var __decorateClass$2 = (decorators, target, key, kind) => {
21
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
22
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
23
+ if (decorator = decorators[i])
24
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
25
+ if (kind && result) __defProp$1(target, key, result);
26
+ return result;
27
+ };
28
+ const DEFAULT_EXPIRATION_MINUTES = 15;
29
+ let MagicLinkToken = class extends SmrtObject {
30
+ nostrIdentityId;
31
+ /**
32
+ * SHA-256 hash of the token (never store plaintext)
33
+ */
34
+ tokenHash = "";
35
+ /**
36
+ * Email this token was sent to
37
+ */
38
+ email = "";
39
+ /**
40
+ * When this token expires
41
+ */
42
+ expiresAt = new Date(
43
+ Date.now() + DEFAULT_EXPIRATION_MINUTES * 60 * 1e3
44
+ );
45
+ /**
46
+ * When this token was used (null = unused)
47
+ */
48
+ usedAt = null;
49
+ /**
50
+ * IP address that requested the token (for rate limiting)
51
+ */
52
+ requestedFromIp = "";
53
+ /**
54
+ * When this token was created (for rate limiting)
55
+ */
56
+ createdAt = /* @__PURE__ */ new Date();
57
+ constructor(options = {}) {
58
+ super(options);
59
+ if (options.nostrIdentityId) this.nostrIdentityId = options.nostrIdentityId;
60
+ if (options.tokenHash) this.tokenHash = options.tokenHash;
61
+ if (options.email) this.email = options.email;
62
+ if (options.expiresAt) this.expiresAt = options.expiresAt;
63
+ if (options.usedAt !== void 0) this.usedAt = options.usedAt;
64
+ if (options.requestedFromIp) this.requestedFromIp = options.requestedFromIp;
65
+ if (options.createdAt) this.createdAt = options.createdAt;
66
+ }
67
+ /**
68
+ * Hash a token using SHA-256
69
+ */
70
+ static hashToken(token) {
71
+ return createHash("sha256").update(token).digest("hex");
72
+ }
73
+ /**
74
+ * Generate a new magic link token
75
+ * @param nostrIdentity - The identity this token is for
76
+ * @param email - The email address to send to
77
+ * @param options - Additional options
78
+ */
79
+ static async generate(nostrIdentity, email, options = {}) {
80
+ const tokenBytes = randomBytes(32);
81
+ const token = tokenBytes.toString("base64url");
82
+ const tokenHash = MagicLinkToken.hashToken(token);
83
+ const expiresInMinutes = options.expiresInMinutes ?? DEFAULT_EXPIRATION_MINUTES;
84
+ const expiresAt = new Date(Date.now() + expiresInMinutes * 60 * 1e3);
85
+ const magicLinkToken = new MagicLinkToken({
86
+ db: options.db ?? nostrIdentity.options?.db,
87
+ nostrIdentityId: nostrIdentity.id,
88
+ tokenHash,
89
+ email: email.toLowerCase(),
90
+ expiresAt,
91
+ requestedFromIp: options.requestedFromIp || ""
92
+ });
93
+ await magicLinkToken.initialize();
94
+ await magicLinkToken.save();
95
+ return { token, magicLinkToken };
96
+ }
97
+ /**
98
+ * Verify a token and return the MagicLinkToken if valid
99
+ * @param token - The plaintext token to verify
100
+ * @param options - Database options
101
+ * @returns The MagicLinkToken if valid, null otherwise
102
+ */
103
+ static async verify(token, options = {}) {
104
+ const tokenHash = MagicLinkToken.hashToken(token);
105
+ const { MagicLinkTokenCollection: MagicLinkTokenCollection2 } = await Promise.resolve().then(() => MagicLinkTokenCollection$1);
106
+ const collection = await MagicLinkTokenCollection2.create(options);
107
+ const magicLinkToken = await collection.findOne({
108
+ where: { tokenHash }
109
+ });
110
+ if (!magicLinkToken) {
111
+ return null;
112
+ }
113
+ if (!magicLinkToken.isValid()) {
114
+ return null;
115
+ }
116
+ return magicLinkToken;
117
+ }
118
+ /**
119
+ * Check if this token is valid (not expired and not used)
120
+ */
121
+ isValid() {
122
+ if (this.usedAt !== null) {
123
+ return false;
124
+ }
125
+ if (/* @__PURE__ */ new Date() > this.expiresAt) {
126
+ return false;
127
+ }
128
+ return true;
129
+ }
130
+ /**
131
+ * Check if this token has expired
132
+ */
133
+ isExpired() {
134
+ return /* @__PURE__ */ new Date() > this.expiresAt;
135
+ }
136
+ /**
137
+ * Check if this token has been used
138
+ */
139
+ isUsed() {
140
+ return this.usedAt !== null;
141
+ }
142
+ /**
143
+ * Mark this token as used
144
+ */
145
+ async markUsed() {
146
+ this.usedAt = /* @__PURE__ */ new Date();
147
+ await this.save();
148
+ }
149
+ /**
150
+ * Get the NostrIdentity this token is for
151
+ */
152
+ async getNostrIdentity() {
153
+ return await this.getRelated("nostrIdentityId");
154
+ }
155
+ /**
156
+ * Get time remaining until expiration in seconds
157
+ */
158
+ getTimeRemainingSeconds() {
159
+ const now = /* @__PURE__ */ new Date();
160
+ const diff = this.expiresAt.getTime() - now.getTime();
161
+ return Math.max(0, Math.floor(diff / 1e3));
162
+ }
163
+ };
164
+ __decorateClass$2([
165
+ foreignKey("NostrIdentity", { required: true })
166
+ ], MagicLinkToken.prototype, "nostrIdentityId", 2);
167
+ MagicLinkToken = __decorateClass$2([
168
+ smrt({
169
+ tableName: "magic_link_tokens",
170
+ api: { exclude: ["*"] },
171
+ // No API access - internal only
172
+ mcp: { exclude: ["*"] },
173
+ cli: { include: ["list"] }
174
+ // Admin visibility only
175
+ })
176
+ ], MagicLinkToken);
177
+ class MagicLinkTokenCollection extends SmrtCollection {
178
+ static _itemClass = MagicLinkToken;
179
+ /**
180
+ * Find token by hash
181
+ */
182
+ async findByTokenHash(tokenHash) {
183
+ return await this.findOne({
184
+ where: { tokenHash }
185
+ });
186
+ }
187
+ /**
188
+ * Find active (non-expired, non-used) tokens for an email
189
+ */
190
+ async findActiveForEmail(email) {
191
+ const tokens = await this.list({
192
+ where: { email: email.toLowerCase() }
193
+ });
194
+ return tokens.filter((token) => token.isValid());
195
+ }
196
+ /**
197
+ * Find tokens for a NostrIdentity
198
+ */
199
+ async findByIdentity(nostrIdentityId) {
200
+ return await this.list({
201
+ where: { nostrIdentityId }
202
+ });
203
+ }
204
+ /**
205
+ * Count tokens created for an email within a time window (for rate limiting)
206
+ * @param email - Email address
207
+ * @param withinMinutes - Time window in minutes
208
+ */
209
+ async countRecentByEmail(email, withinMinutes) {
210
+ const cutoff = new Date(Date.now() - withinMinutes * 60 * 1e3);
211
+ const tokens = await this.list({
212
+ where: { email: email.toLowerCase() }
213
+ });
214
+ return tokens.filter((token) => {
215
+ const createdAt = token.createdAt;
216
+ return createdAt && new Date(createdAt) > cutoff;
217
+ }).length;
218
+ }
219
+ /**
220
+ * Count tokens created from an IP within a time window (for rate limiting)
221
+ * @param ip - IP address
222
+ * @param withinMinutes - Time window in minutes
223
+ */
224
+ async countRecentByIp(ip, withinMinutes) {
225
+ const cutoff = new Date(Date.now() - withinMinutes * 60 * 1e3);
226
+ const tokens = await this.list({
227
+ where: { requestedFromIp: ip }
228
+ });
229
+ return tokens.filter((token) => {
230
+ const createdAt = token.createdAt;
231
+ return createdAt && new Date(createdAt) > cutoff;
232
+ }).length;
233
+ }
234
+ /**
235
+ * Verify a token and return it if valid
236
+ */
237
+ async verify(token) {
238
+ const tokenHash = MagicLinkToken.hashToken(token);
239
+ const magicLinkToken = await this.findByTokenHash(tokenHash);
240
+ if (!magicLinkToken) {
241
+ return null;
242
+ }
243
+ if (!magicLinkToken.isValid()) {
244
+ return null;
245
+ }
246
+ return magicLinkToken;
247
+ }
248
+ /**
249
+ * Clean up expired tokens by deleting them
250
+ * @returns Number of tokens deleted
251
+ */
252
+ async cleanupExpired() {
253
+ const allTokens = await this.list({});
254
+ let deleted = 0;
255
+ for (const token of allTokens) {
256
+ if (token.isExpired() || token.isUsed()) {
257
+ await token.delete();
258
+ deleted++;
259
+ }
260
+ }
261
+ return deleted;
262
+ }
263
+ /**
264
+ * Revoke all tokens for a NostrIdentity (e.g., when identity is deleted)
265
+ */
266
+ async revokeForIdentity(nostrIdentityId) {
267
+ const tokens = await this.findByIdentity(nostrIdentityId);
268
+ let revoked = 0;
269
+ for (const token of tokens) {
270
+ if (!token.isUsed()) {
271
+ await token.markUsed();
272
+ revoked++;
273
+ }
274
+ }
275
+ return revoked;
276
+ }
277
+ /**
278
+ * Check if rate limit is exceeded for email
279
+ * @param email - Email address
280
+ * @param maxPerHour - Maximum tokens allowed per hour (default: 5)
281
+ */
282
+ async isRateLimitedByEmail(email, maxPerHour = 5) {
283
+ const count = await this.countRecentByEmail(email, 60);
284
+ return count >= maxPerHour;
285
+ }
286
+ /**
287
+ * Check if rate limit is exceeded for IP
288
+ * @param ip - IP address
289
+ * @param maxPerHour - Maximum tokens allowed per hour (default: 10)
290
+ */
291
+ async isRateLimitedByIp(ip, maxPerHour = 10) {
292
+ const count = await this.countRecentByIp(ip, 60);
293
+ return count >= maxPerHour;
294
+ }
295
+ }
296
+ const MagicLinkTokenCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
297
+ __proto__: null,
298
+ MagicLinkTokenCollection
299
+ }, Symbol.toStringTag, { value: "Module" }));
300
+ function createMagicLinkService(config) {
301
+ const {
302
+ baseUrl,
303
+ verifyPath = "/auth/verify",
304
+ expiresInMinutes = 15,
305
+ maxTokensPerEmailPerHour = 5,
306
+ maxTokensPerIpPerHour = 10,
307
+ masterSecret,
308
+ sendEmail,
309
+ db
310
+ } = config;
311
+ return {
312
+ async initiate(email, options = {}) {
313
+ const normalizedEmail = email.toLowerCase().trim();
314
+ const { requestedFromIp, nip05Username } = options;
315
+ const identityCollection = await NostrIdentityCollection.create({ db });
316
+ const tokenCollection = await MagicLinkTokenCollection.create({ db });
317
+ if (await tokenCollection.isRateLimitedByEmail(
318
+ normalizedEmail,
319
+ maxTokensPerEmailPerHour
320
+ )) {
321
+ return {
322
+ success: false,
323
+ created: false,
324
+ error: "Too many requests for this email. Please try again later.",
325
+ errorCode: "RATE_LIMITED_EMAIL"
326
+ };
327
+ }
328
+ if (requestedFromIp && await tokenCollection.isRateLimitedByIp(
329
+ requestedFromIp,
330
+ maxTokensPerIpPerHour
331
+ )) {
332
+ return {
333
+ success: false,
334
+ created: false,
335
+ error: "Too many requests from this IP. Please try again later.",
336
+ errorCode: "RATE_LIMITED_IP"
337
+ };
338
+ }
339
+ let nostrIdentity = await identityCollection.findByEmail(normalizedEmail);
340
+ let profile = null;
341
+ let created = false;
342
+ if (!nostrIdentity) {
343
+ const { Person: Person2 } = await Promise.resolve().then(() => ProfileTypes);
344
+ profile = new Person2({
345
+ db,
346
+ email: normalizedEmail,
347
+ name: normalizedEmail.split("@")[0]
348
+ // Default name from email
349
+ });
350
+ await profile.initialize();
351
+ await profile.save();
352
+ const keypair = generateNostrKeypair();
353
+ const encrypted = encryptPrivkey(keypair.privkey, masterSecret);
354
+ const { NostrIdentity: NostrIdentityClass } = await import("./NostrIdentityCollection-DadQBHWy.js").then((n) => n.o);
355
+ nostrIdentity = new NostrIdentityClass({
356
+ db,
357
+ profileId: profile.id,
358
+ pubkey: keypair.pubkey,
359
+ encryptedPrivkey: encrypted.ciphertext,
360
+ encryptionIv: encrypted.iv,
361
+ encryptionTag: encrypted.tag,
362
+ email: normalizedEmail,
363
+ nip05Username: nip05Username?.toLowerCase() || normalizedEmail.split("@")[0]
364
+ });
365
+ await nostrIdentity.initialize();
366
+ await nostrIdentity.save();
367
+ created = true;
368
+ } else {
369
+ profile = await nostrIdentity.getProfile();
370
+ }
371
+ const { token } = await MagicLinkToken.generate(
372
+ nostrIdentity,
373
+ normalizedEmail,
374
+ {
375
+ expiresInMinutes,
376
+ requestedFromIp,
377
+ db
378
+ }
379
+ );
380
+ const magicLink = `${baseUrl}${verifyPath}?token=${encodeURIComponent(token)}`;
381
+ try {
382
+ await sendEmail(normalizedEmail, magicLink);
383
+ } catch (emailError) {
384
+ return {
385
+ success: false,
386
+ created,
387
+ nostrIdentity,
388
+ profile: profile || void 0,
389
+ error: "Failed to send email. Please try again.",
390
+ errorCode: "EMAIL_SEND_FAILED"
391
+ };
392
+ }
393
+ return {
394
+ success: true,
395
+ created,
396
+ nostrIdentity,
397
+ profile: profile || void 0
398
+ };
399
+ },
400
+ async verify(token) {
401
+ const magicLinkToken = await MagicLinkToken.verify(token, { db });
402
+ if (!magicLinkToken) {
403
+ const tokenHash = MagicLinkToken.hashToken(token);
404
+ const tokenCollection = await MagicLinkTokenCollection.create({ db });
405
+ const existingToken = await tokenCollection.findByTokenHash(tokenHash);
406
+ if (!existingToken) {
407
+ return {
408
+ success: false,
409
+ error: "Invalid or expired token.",
410
+ errorCode: "NOT_FOUND"
411
+ };
412
+ }
413
+ if (existingToken.isUsed()) {
414
+ return {
415
+ success: false,
416
+ error: "This link has already been used.",
417
+ errorCode: "USED_TOKEN"
418
+ };
419
+ }
420
+ if (existingToken.isExpired()) {
421
+ return {
422
+ success: false,
423
+ error: "This link has expired. Please request a new one.",
424
+ errorCode: "EXPIRED_TOKEN"
425
+ };
426
+ }
427
+ return {
428
+ success: false,
429
+ error: "Invalid token.",
430
+ errorCode: "INVALID_TOKEN"
431
+ };
432
+ }
433
+ await magicLinkToken.markUsed();
434
+ const nostrIdentity = await magicLinkToken.getNostrIdentity();
435
+ if (!nostrIdentity) {
436
+ return {
437
+ success: false,
438
+ error: "Identity not found.",
439
+ errorCode: "NOT_FOUND"
440
+ };
441
+ }
442
+ await nostrIdentity.markVerified();
443
+ const profile = await nostrIdentity.getProfile();
444
+ const keypair = nostrIdentity.getKeypair(masterSecret);
445
+ return {
446
+ success: true,
447
+ profile: profile || void 0,
448
+ nostrIdentity,
449
+ keypair
450
+ };
451
+ }
452
+ };
453
+ }
454
+ function createNip05Handler(config) {
455
+ const { db, defaultRelays = [], cacheTtlSeconds = 300 } = config;
456
+ return async function handleNip05Request(request) {
457
+ const { name } = request;
458
+ const collection = await NostrIdentityCollection.create({ db });
459
+ const response = await collection.getNip05Response(name || void 0);
460
+ if (defaultRelays.length > 0 && Object.keys(response.names).length > 0) {
461
+ response.relays = {};
462
+ for (const pubkey of Object.values(response.names)) {
463
+ response.relays[pubkey] = defaultRelays;
464
+ }
465
+ }
466
+ if (name && Object.keys(response.names).length === 0) {
467
+ return {
468
+ body: { names: {} },
469
+ headers: {
470
+ "Content-Type": "application/json",
471
+ "Access-Control-Allow-Origin": "*",
472
+ "Cache-Control": `public, max-age=${cacheTtlSeconds}`
473
+ },
474
+ status: 200
475
+ // NIP-05 spec says return 200 with empty names, not 404
476
+ };
477
+ }
478
+ return {
479
+ body: response,
480
+ headers: {
481
+ "Content-Type": "application/json",
482
+ "Access-Control-Allow-Origin": "*",
483
+ "Cache-Control": `public, max-age=${cacheTtlSeconds}`
484
+ },
485
+ status: 200
486
+ };
487
+ };
488
+ }
489
+ function isValidNip05Identifier(identifier) {
490
+ const parts = identifier.split("@");
491
+ if (parts.length !== 2) {
492
+ return false;
493
+ }
494
+ const [localPart, domain] = parts;
495
+ if (!/^[a-z0-9_-]+$/i.test(localPart)) {
496
+ return false;
497
+ }
498
+ if (!/^[a-z0-9.-]+\.[a-z]{2,}$/i.test(domain)) {
499
+ return false;
500
+ }
501
+ return true;
502
+ }
503
+ function parseNip05Identifier(identifier) {
504
+ if (!isValidNip05Identifier(identifier)) {
505
+ return null;
506
+ }
507
+ const [localPart, domain] = identifier.split("@");
508
+ return { localPart, domain };
509
+ }
510
+ var __defProp = Object.defineProperty;
511
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
512
+ var __decorateClass$1 = (decorators, target, key, kind) => {
513
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
514
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
515
+ if (decorator = decorators[i])
516
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
517
+ if (kind && result) __defProp(target, key, result);
518
+ return result;
519
+ };
520
+ let OidcIdentity = class extends SmrtObject {
521
+ profileId;
522
+ provider = "";
523
+ issuer = "";
524
+ subject = "";
525
+ email = "";
526
+ lastUsedAt = null;
527
+ constructor(options = {}) {
528
+ super(options);
529
+ if (options.profileId) this.profileId = options.profileId;
530
+ if (options.provider) this.provider = options.provider;
531
+ if (options.issuer) this.issuer = options.issuer;
532
+ if (options.subject) this.subject = options.subject;
533
+ if (options.email) this.email = options.email;
534
+ if (options.lastUsedAt !== void 0) this.lastUsedAt = options.lastUsedAt;
535
+ }
536
+ /**
537
+ * Get the linked Profile
538
+ */
539
+ async getProfile() {
540
+ return await this.getRelated("profileId");
541
+ }
542
+ /**
543
+ * Find identity by issuer and subject
544
+ */
545
+ static async findBySubject(issuer, subject, options = {}) {
546
+ const { OidcIdentityCollection: OidcIdentityCollection2 } = await Promise.resolve().then(() => OidcIdentityCollection$1);
547
+ const collection = await OidcIdentityCollection2.create(options);
548
+ return await collection.findOne({
549
+ where: { issuer, subject }
550
+ });
551
+ }
552
+ /**
553
+ * Find or create identity for a profile
554
+ */
555
+ static async findOrCreate(profile, oidcData, options = {}) {
556
+ const existing = await OidcIdentity.findBySubject(
557
+ oidcData.issuer,
558
+ oidcData.subject,
559
+ options
560
+ );
561
+ if (existing) {
562
+ existing.lastUsedAt = /* @__PURE__ */ new Date();
563
+ if (oidcData.email) existing.email = oidcData.email;
564
+ await existing.save();
565
+ return existing;
566
+ }
567
+ const identity = new OidcIdentity({
568
+ ...options,
569
+ profileId: profile.id,
570
+ provider: oidcData.provider,
571
+ issuer: oidcData.issuer,
572
+ subject: oidcData.subject,
573
+ email: oidcData.email || ""
574
+ });
575
+ await identity.initialize();
576
+ await identity.save();
577
+ return identity;
578
+ }
579
+ /**
580
+ * Record usage of this identity
581
+ */
582
+ async recordUsage() {
583
+ this.lastUsedAt = /* @__PURE__ */ new Date();
584
+ await this.save();
585
+ }
586
+ };
587
+ __decorateClass$1([
588
+ foreignKey("Profile", { required: true })
589
+ ], OidcIdentity.prototype, "profileId", 2);
590
+ __decorateClass$1([
591
+ field({ type: "text" })
592
+ ], OidcIdentity.prototype, "provider", 2);
593
+ __decorateClass$1([
594
+ field({ type: "text" })
595
+ ], OidcIdentity.prototype, "issuer", 2);
596
+ __decorateClass$1([
597
+ field({ type: "text" })
598
+ ], OidcIdentity.prototype, "subject", 2);
599
+ __decorateClass$1([
600
+ field({ type: "text" })
601
+ ], OidcIdentity.prototype, "email", 2);
602
+ __decorateClass$1([
603
+ field({ type: "datetime", nullable: true })
604
+ ], OidcIdentity.prototype, "lastUsedAt", 2);
605
+ OidcIdentity = __decorateClass$1([
606
+ smrt({
607
+ tableName: "oidc_identities",
608
+ api: { exclude: ["delete"] },
609
+ mcp: { include: ["list", "get"] },
610
+ cli: { include: ["list", "get"] }
611
+ })
612
+ ], OidcIdentity);
613
+ async function resolveIdentity(context) {
614
+ const options = { db: context.db };
615
+ if (context.apiKey) {
616
+ const apiKey = await ApiKey.verify(context.apiKey, options);
617
+ if (apiKey) {
618
+ const profile = await apiKey.getProfile();
619
+ if (profile) {
620
+ return {
621
+ profile,
622
+ source: "api_key",
623
+ apiKey
624
+ };
625
+ }
626
+ }
627
+ }
628
+ if (context.oidcSession?.sub && context.oidcSession?.iss) {
629
+ const oidcIdentity = await OidcIdentity.findBySubject(
630
+ context.oidcSession.iss,
631
+ context.oidcSession.sub,
632
+ options
633
+ );
634
+ if (oidcIdentity) {
635
+ await oidcIdentity.recordUsage();
636
+ const profile = await oidcIdentity.getProfile();
637
+ if (profile) {
638
+ return {
639
+ profile,
640
+ source: "oidc",
641
+ oidcIdentity
642
+ };
643
+ }
644
+ }
645
+ }
646
+ if (context.nostrAuth?.event && context.nostrAuth?.challenge) {
647
+ const { event, challenge } = context.nostrAuth;
648
+ const verifyResult = verifyAuthEvent(event, challenge);
649
+ if (verifyResult.valid) {
650
+ const nostrIdentity = await NostrIdentity.findByPubkey(
651
+ event.pubkey,
652
+ options
653
+ );
654
+ if (nostrIdentity) {
655
+ await nostrIdentity.recordUsage();
656
+ const profile = await nostrIdentity.getProfile();
657
+ if (profile) {
658
+ return {
659
+ profile,
660
+ source: "nostr",
661
+ nostrIdentity
662
+ };
663
+ }
664
+ }
665
+ }
666
+ }
667
+ if (context.actor) {
668
+ const profile = await findProfileByExternalId(
669
+ "github",
670
+ context.actor,
671
+ options
672
+ );
673
+ if (profile) {
674
+ return {
675
+ profile,
676
+ source: "actor"
677
+ };
678
+ }
679
+ }
680
+ return {
681
+ profile: null,
682
+ source: "none"
683
+ };
684
+ }
685
+ async function findProfileByExternalId(provider, externalId, options) {
686
+ const { OidcIdentityCollection: OidcIdentityCollection2 } = await Promise.resolve().then(() => OidcIdentityCollection$1);
687
+ const oidcCollection = await OidcIdentityCollection2.create(options);
688
+ const identities = await oidcCollection.findByProvider(provider);
689
+ for (const identity of identities) {
690
+ if (identity.subject === externalId || identity.email?.includes(externalId)) {
691
+ const profile = await identity.getProfile();
692
+ if (profile) return profile;
693
+ }
694
+ }
695
+ const { ProfileCollection } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.d);
696
+ const profileCollection = await ProfileCollection.create(options);
697
+ const profiles = await profileCollection.list({
698
+ where: {
699
+ email: `${externalId}@users.noreply.github.com`
700
+ },
701
+ limit: 1
702
+ });
703
+ if (profiles.length > 0) {
704
+ return profiles[0];
705
+ }
706
+ return null;
707
+ }
708
+ async function createProfileFromOidc(claims, provider, options) {
709
+ const existingIdentity = await OidcIdentity.findBySubject(
710
+ claims.iss,
711
+ claims.sub,
712
+ options
713
+ );
714
+ if (existingIdentity) {
715
+ const profile2 = await existingIdentity.getProfile();
716
+ if (profile2) {
717
+ if (claims.email) existingIdentity.email = claims.email;
718
+ existingIdentity.lastUsedAt = /* @__PURE__ */ new Date();
719
+ await existingIdentity.save();
720
+ return {
721
+ profile: profile2,
722
+ oidcIdentity: existingIdentity,
723
+ created: false
724
+ };
725
+ }
726
+ }
727
+ if (claims.email && claims.email_verified) {
728
+ const { ProfileCollection } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.d);
729
+ const profileCollection = await ProfileCollection.create(options);
730
+ const existingProfile = await profileCollection.findByEmail(claims.email);
731
+ if (existingProfile) {
732
+ const oidcIdentity2 = await OidcIdentity.findOrCreate(
733
+ existingProfile,
734
+ {
735
+ provider,
736
+ issuer: claims.iss,
737
+ subject: claims.sub,
738
+ email: claims.email
739
+ },
740
+ options
741
+ );
742
+ return {
743
+ profile: existingProfile,
744
+ oidcIdentity: oidcIdentity2,
745
+ created: false
746
+ };
747
+ }
748
+ }
749
+ const { Person: Person2 } = await Promise.resolve().then(() => ProfileTypes);
750
+ const { ProfileTypeCollection: ProfileTypeCollection2 } = await Promise.resolve().then(() => ProfileTypeCollection$1);
751
+ const typeCollection = await ProfileTypeCollection2.create(options);
752
+ let personType = await typeCollection.getBySlug("person");
753
+ if (!personType) {
754
+ const { ProfileType: ProfileType2 } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.c);
755
+ personType = new ProfileType2({
756
+ ...options,
757
+ slug: "person",
758
+ name: "Person",
759
+ description: "Individual person profile"
760
+ });
761
+ await personType.initialize();
762
+ await personType.save();
763
+ }
764
+ const profile = new Person2({
765
+ ...options,
766
+ typeId: personType.id,
767
+ email: claims.email || "",
768
+ name: claims.name || claims.preferred_username || claims.sub
769
+ });
770
+ await profile.initialize();
771
+ await profile.save();
772
+ const oidcIdentity = await OidcIdentity.findOrCreate(
773
+ profile,
774
+ {
775
+ provider,
776
+ issuer: claims.iss,
777
+ subject: claims.sub,
778
+ email: claims.email
779
+ },
780
+ options
781
+ );
782
+ return {
783
+ profile,
784
+ oidcIdentity,
785
+ created: true
786
+ };
787
+ }
788
+ async function createProfileFromNostr(email, nostrData, options) {
789
+ const normalizedEmail = email.toLowerCase();
790
+ const existingIdentity = await NostrIdentity.findByEmail(
791
+ normalizedEmail,
792
+ options
793
+ );
794
+ if (existingIdentity) {
795
+ const profile2 = await existingIdentity.getProfile();
796
+ if (profile2) {
797
+ return {
798
+ profile: profile2,
799
+ nostrIdentity: existingIdentity,
800
+ created: false
801
+ };
802
+ }
803
+ }
804
+ const { Person: Person2 } = await Promise.resolve().then(() => ProfileTypes);
805
+ const { ProfileTypeCollection: ProfileTypeCollection2 } = await Promise.resolve().then(() => ProfileTypeCollection$1);
806
+ const typeCollection = await ProfileTypeCollection2.create(options);
807
+ let personType = await typeCollection.getBySlug("person");
808
+ if (!personType) {
809
+ const { ProfileType: ProfileType2 } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.c);
810
+ personType = new ProfileType2({
811
+ ...options,
812
+ slug: "person",
813
+ name: "Person",
814
+ description: "Individual person profile"
815
+ });
816
+ await personType.initialize();
817
+ await personType.save();
818
+ }
819
+ const profile = new Person2({
820
+ ...options,
821
+ typeId: personType.id,
822
+ email: normalizedEmail,
823
+ name: normalizedEmail.split("@")[0]
824
+ // Default name from email
825
+ });
826
+ await profile.initialize();
827
+ await profile.save();
828
+ const nostrIdentity = new NostrIdentity({
829
+ ...options,
830
+ profileId: profile.id,
831
+ pubkey: nostrData.pubkey,
832
+ encryptedPrivkey: nostrData.encryptedPrivkey,
833
+ encryptionIv: nostrData.encryptionIv,
834
+ encryptionTag: nostrData.encryptionTag,
835
+ email: normalizedEmail,
836
+ nip05Username: nostrData.nip05Username?.toLowerCase() || normalizedEmail.split("@")[0]
837
+ });
838
+ await nostrIdentity.initialize();
839
+ await nostrIdentity.save();
840
+ return {
841
+ profile,
842
+ nostrIdentity,
843
+ created: true
844
+ };
845
+ }
846
+ class OidcIdentityCollection extends SmrtCollection {
847
+ static _itemClass = OidcIdentity;
848
+ /**
849
+ * Find identities for a profile
850
+ */
851
+ async findByProfile(profileId) {
852
+ return await this.list({
853
+ where: { profileId }
854
+ });
855
+ }
856
+ /**
857
+ * Find identity by issuer and subject
858
+ */
859
+ async findBySubject(issuer, subject) {
860
+ return await this.findOne({
861
+ where: { issuer, subject }
862
+ });
863
+ }
864
+ /**
865
+ * Find identities by provider
866
+ */
867
+ async findByProvider(provider) {
868
+ return await this.list({
869
+ where: { provider }
870
+ });
871
+ }
872
+ /**
873
+ * Link a new OIDC identity to a profile
874
+ */
875
+ async linkToProfile(profile, oidcData) {
876
+ const existing = await this.findBySubject(
877
+ oidcData.issuer,
878
+ oidcData.subject
879
+ );
880
+ if (existing) {
881
+ existing.lastUsedAt = /* @__PURE__ */ new Date();
882
+ if (oidcData.email) existing.email = oidcData.email;
883
+ await existing.save();
884
+ return existing;
885
+ }
886
+ const identity = new OidcIdentity({
887
+ ...this.options,
888
+ profileId: profile.id,
889
+ provider: oidcData.provider,
890
+ issuer: oidcData.issuer,
891
+ subject: oidcData.subject,
892
+ email: oidcData.email || ""
893
+ });
894
+ await identity.initialize();
895
+ await identity.save();
896
+ return identity;
897
+ }
898
+ /**
899
+ * Unlink an OIDC identity from a profile
900
+ */
901
+ async unlinkFromProfile(profileId, issuer, subject) {
902
+ const identity = await this.findOne({
903
+ where: { profileId, issuer, subject }
904
+ });
905
+ if (identity) {
906
+ await identity.delete();
907
+ return true;
908
+ }
909
+ return false;
910
+ }
911
+ }
912
+ const OidcIdentityCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
913
+ __proto__: null,
914
+ OidcIdentityCollection
915
+ }, Symbol.toStringTag, { value: "Module" }));
916
+ class ProfileTypeCollection extends SmrtCollection {
917
+ static _itemClass = ProfileType;
918
+ /**
919
+ * Get profile type by slug
920
+ *
921
+ * @param slug - The slug to search for
922
+ * @returns ProfileType instance or null
923
+ */
924
+ async getBySlug(slug) {
925
+ return await this.get({ slug });
926
+ }
927
+ /**
928
+ * Get or create a profile type by slug
929
+ *
930
+ * @param slug - The slug to search for
931
+ * @param defaults - Default values if creating
932
+ * @returns ProfileType instance
933
+ */
934
+ async getOrCreateBySlug(slug, defaults) {
935
+ const existing = await this.getBySlug(slug);
936
+ if (existing) return existing;
937
+ const profileType = await this.create({ slug, ...defaults });
938
+ await profileType.save();
939
+ return profileType;
940
+ }
941
+ }
942
+ const ProfileTypeCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
943
+ __proto__: null,
944
+ ProfileTypeCollection
945
+ }, Symbol.toStringTag, { value: "Module" }));
946
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
947
+ var __decorateClass = (decorators, target, key, kind) => {
948
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
949
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
950
+ if (decorator = decorators[i])
951
+ result = decorator(result) || result;
952
+ return result;
953
+ };
954
+ let Person = class extends Profile {
955
+ constructor(options = {}) {
956
+ super(options);
957
+ }
958
+ };
959
+ Person = __decorateClass([
960
+ smrt({
961
+ tableStrategy: "sti"
962
+ })
963
+ ], Person);
964
+ let Organization = class extends Profile {
965
+ constructor(options = {}) {
966
+ super(options);
967
+ }
968
+ };
969
+ Organization = __decorateClass([
970
+ smrt({
971
+ tableStrategy: "sti"
972
+ })
973
+ ], Organization);
974
+ let Bot = class extends Profile {
975
+ constructor(options = {}) {
976
+ super(options);
977
+ }
978
+ };
979
+ Bot = __decorateClass([
980
+ smrt({
981
+ tableStrategy: "sti"
982
+ })
983
+ ], Bot);
984
+ const ProfileTypes = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
985
+ __proto__: null,
986
+ get Bot() {
987
+ return Bot;
988
+ },
989
+ get Organization() {
990
+ return Organization;
991
+ },
992
+ get Person() {
993
+ return Person;
994
+ }
995
+ }, Symbol.toStringTag, { value: "Module" }));
996
+ export {
997
+ Bot as B,
998
+ MagicLinkToken as M,
999
+ OidcIdentity as O,
1000
+ Person as P,
1001
+ MagicLinkTokenCollection as a,
1002
+ OidcIdentityCollection as b,
1003
+ Organization as c,
1004
+ ProfileTypeCollection as d,
1005
+ createMagicLinkService as e,
1006
+ createNip05Handler as f,
1007
+ createProfileFromNostr as g,
1008
+ createProfileFromOidc as h,
1009
+ isValidNip05Identifier as i,
1010
+ OidcIdentityCollection$1 as j,
1011
+ parseNip05Identifier as p,
1012
+ resolveIdentity as r
1013
+ };
1014
+ //# sourceMappingURL=index-jFtOWsAV.js.map