move stuff

This commit is contained in:
shocknet-justin 2025-06-15 12:55:20 -04:00
parent d7cb1c38f3
commit 6a1cedd718
6 changed files with 222 additions and 173 deletions

View file

@ -5,7 +5,7 @@ import * as Types from '../proto/autogenerated/ts/types.js'
import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js'; import NewNostrTransport, { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
import { ERROR, getLogger } from "./services/helpers/logger.js"; import { ERROR, getLogger } from "./services/helpers/logger.js";
import { NdebitData, NofferData } from "@shocknet/clink-sdk"; import { NdebitData, NofferData } from "@shocknet/clink-sdk";
import { ManagementManager } from "./services/managementManager.js"; import { ManagementManager } from "./services/main/managementManager.js";
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend, Ping: () => Promise<void> } => { export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings, onClientEvent: (e: { requestId: string }, fromPub: string) => void): { Stop: () => void, Send: NostrSend, Ping: () => Promise<void> } => {
const log = getLogger({}) const log = getLogger({})
@ -40,7 +40,8 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
}) })
let nostr: Nostr; let nostr: Nostr;
const managementManager = new ManagementManager((...args) => nostr.Send(...args), nostrSettings); const managementManager = new ManagementManager((...args: Parameters<NostrSend>) => nostr.Send(...args), nostrSettings, mainHandler.storage.managementStorage);
mainHandler.managementManager = managementManager;
nostr = new Nostr(nostrSettings, mainHandler.utils, event => { nostr = new Nostr(nostrSettings, mainHandler.utils, event => {
let j: NostrRequest let j: NostrRequest
@ -76,7 +77,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
}, event.startAtNano, event.startAtMs) }, event.startAtNano, event.startAtMs)
}) })
return { Stop: () => nostr.Stop, Send: (...args) => nostr.Send(...args), Ping: () => nostr.Ping() } return { Stop: () => nostr.Stop, Send: (...args: Parameters<NostrSend>) => nostr.Send(...args), Ping: () => nostr.Ping() }
} }

View file

@ -25,6 +25,7 @@ import { defaultInvoiceExpiry } from "../storage/paymentStorage.js"
import { DebitManager } from "./debitManager.js" import { DebitManager } from "./debitManager.js"
import { OfferManager } from "./offerManager.js" import { OfferManager } from "./offerManager.js"
import webRTC from "../webRTC/index.js" import webRTC from "../webRTC/index.js"
import { ManagementManager } from "./managementManager.js"
type UserOperationsSub = { type UserOperationsSub = {
id: string id: string
@ -51,17 +52,16 @@ export default class {
liquidityProvider: LiquidityProvider liquidityProvider: LiquidityProvider
debitManager: DebitManager debitManager: DebitManager
offerManager: OfferManager offerManager: OfferManager
managementManager?: ManagementManager
utils: Utils utils: Utils
rugPullTracker: RugPullTracker rugPullTracker: RugPullTracker
unlocker: Unlocker unlocker: Unlocker
//webRTC: webRTC //webRTC: webRTC
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") } nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
nostrProcessPing: (() => Promise<void>) | null = null nostrProcessPing: (() => Promise<void>) | null = null
constructor(settings: MainSettings, storage: Storage, adminManager: AdminManager, utils: Utils, unlocker: Unlocker) { constructor(settings: MainSettings, utils: Utils, unlocker: Unlocker) {
this.settings = settings this.settings = settings
this.storage = storage
this.utils = utils this.utils = utils
this.adminManager = adminManager
this.unlocker = unlocker this.unlocker = unlocker
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b) const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b)
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance) this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance)
@ -340,6 +340,10 @@ export default class {
log({ unsigned: event }) log({ unsigned: event })
this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined) this.nostrSend({ type: 'app', appId: invoice.linkedApplication.app_id }, { type: 'event', event }, zapInfo.relays || undefined)
} }
async Start() {
// ... existing code ...
}
} }

View file

@ -0,0 +1,195 @@
import { getRepository } from "typeorm";
import { User } from "../storage/entity/User.js";
import { UserOffer } from "../storage/entity/UserOffer.js";
import { ManagementGrant } from "../storage/entity/ManagementGrant.js";
import { NostrEvent, NostrSend, NostrSettings } from "../nostr/handler.js";
import { ManagementStorage } from "../storage/managementStorage.js";
export class ManagementManager {
private nostrSend: NostrSend;
private settings: NostrSettings;
private storage: ManagementStorage;
constructor(nostrSend: NostrSend, settings: NostrSettings, storage: ManagementStorage) {
this.nostrSend = nostrSend;
this.settings = settings;
this.storage = storage;
}
/**
* Handles an incoming CLINK Manage request
* @param event The raw Nostr event
*/
public async handleRequest(event: NostrEvent) {
const app = this.settings.apps.find((a: any) => a.appId === event.appId);
if (!app) {
console.error(`App with id ${event.appId} not found in settings`);
return; // Cannot proceed
}
if (!this._validateRequest(event)) {
return;
}
const grant = await this._checkGrant(event, app.publicKey);
if (!grant) {
this.sendErrorResponse(event.pubkey, "Permission denied.", app);
return;
}
await this._performAction(event, app);
}
private _validateRequest(event: NostrEvent): boolean {
// TODO: NIP-44 validation or similar
return true;
}
private async _checkGrant(event: NostrEvent, appPubkey: string): Promise<ManagementGrant | null> {
const userIdTag = event.tags.find((t: string[]) => t[0] === 'p');
if (!userIdTag) {
return null;
}
const userId = userIdTag[1];
const grant = await this.storage.getGrant(userId, appPubkey);
if (!grant || (grant.expires_at && grant.expires_at.getTime() < Date.now())) {
return null;
}
return grant;
}
private async _performAction(event: NostrEvent, app: {publicKey: string, appId: string}) {
const actionTag = event.tags.find((t: string[]) => t[0] === 'a');
if (!actionTag) {
console.error("No action specified in event");
return;
}
const action = actionTag[1];
switch (action) {
case "create":
await this._createOffer(event, app);
break;
case "update":
await this._updateOffer(event, app);
break;
case "delete":
await this._deleteOffer(event, app);
break;
default:
console.error(`Unknown action: ${action}`);
this.sendErrorResponse(event.pubkey, `Unknown action: ${action}`, app);
}
}
private async _createOffer(event: NostrEvent, app: {publicKey: string, appId: string}) {
const createDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
if (!createDetailsTag || !createDetailsTag[1]) {
console.error("No details provided for create action");
return;
}
const userId = event.tags.find((t: string[]) => t[0] === 'p')![1];
try {
const offerData = JSON.parse(createDetailsTag[1]);
const offerRepo = getRepository(UserOffer);
const newOffer = offerRepo.create({
...offerData,
user_id: userId,
managing_app_pubkey: app.publicKey
});
await offerRepo.save(newOffer);
this.sendSuccessResponse(event.pubkey, "Offer created successfully", app);
} catch (e) {
console.error("Failed to parse or save offer data", e);
this.sendErrorResponse(event.pubkey, "Failed to create offer", app);
}
}
private async _updateOffer(event: NostrEvent, app: {publicKey: string, appId: string}) {
const updateTags = event.tags.filter((t: string[]) => t[0] === 'd');
if (updateTags.length < 2) {
console.error("Insufficient details for update action");
return;
}
const offerIdToUpdate = updateTags[0][1];
const updateData = JSON.parse(updateTags[1][1]);
const offerRepo = getRepository(UserOffer);
try {
const existingOffer = await offerRepo.findOne({where: { offer_id: offerIdToUpdate }});
if (!existingOffer) {
console.error(`Offer ${offerIdToUpdate} not found`);
return;
}
if (existingOffer.managing_app_pubkey !== app.publicKey) {
console.error(`App ${app.publicKey} not authorized to update offer ${offerIdToUpdate}`);
return;
}
offerRepo.merge(existingOffer, updateData);
await offerRepo.save(existingOffer);
this.sendSuccessResponse(event.pubkey, "Offer updated successfully", app);
} catch (e) {
console.error("Failed to update offer data", e);
this.sendErrorResponse(event.pubkey, "Failed to update offer", app);
}
}
private async _deleteOffer(event: NostrEvent, app: {publicKey: string, appId: string}) {
const deleteDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
if (!deleteDetailsTag || !deleteDetailsTag[1]) {
console.error("No details provided for delete action");
return;
}
const offerIdToDelete = deleteDetailsTag[1];
const offerRepo = getRepository(UserOffer);
try {
const offerToDelete = await offerRepo.findOne({where: { offer_id: offerIdToDelete }});
if (!offerToDelete) {
console.error(`Offer ${offerIdToDelete} not found`);
return;
}
if (offerToDelete.managing_app_pubkey !== app.publicKey) {
console.error(`App ${app.publicKey} not authorized to delete offer ${offerIdToDelete}`);
return;
}
await offerRepo.remove(offerToDelete);
this.sendSuccessResponse(event.pubkey, "Offer deleted successfully", app);
} catch (e) {
console.error("Failed to delete offer", e);
this.sendErrorResponse(event.pubkey, "Failed to delete offer", app);
}
}
private sendSuccessResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
const responseEvent = {
kind: 21003,
pubkey: app.publicKey,
created_at: Math.floor(Date.now() / 1000),
content: JSON.stringify({ status: "success", message }),
tags: [['p', recipient]],
};
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
}
private sendErrorResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
const responseEvent = {
kind: 21003,
pubkey: app.publicKey,
created_at: Math.floor(Date.now() / 1000),
content: JSON.stringify({ status: "error", message }),
tags: [['p', recipient]],
};
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
}
}

View file

@ -1,167 +0,0 @@
import { getRepository } from "typeorm";
import { User } from "./storage/entity/User";
import { UserOffer } from "./storage/entity/UserOffer";
import { ManagementGrant } from "./storage/entity/ManagementGrant";
import { NostrEvent, NostrSend, NostrSettings } from "./nostr/handler.js";
export class ManagementManager {
private nostrSend: NostrSend;
private settings: NostrSettings;
constructor(nostrSend: NostrSend, settings: NostrSettings) {
this.nostrSend = nostrSend;
this.settings = settings;
}
/**
* Handles an incoming CLINK Manage request
* @param event The raw Nostr event
*/
public async handleRequest(event: NostrEvent) {
const app = this.settings.apps.find(a => a.appId === event.appId);
if (!app) {
console.error(`App with id ${event.appId} not found in settings`);
return; // Cannot proceed
}
const appPubkey = app.publicKey;
// Check grant
const userIdTag = event.tags.find((t: string[]) => t[0] === 'p');
if (!userIdTag) {
console.error("No user specified in event");
return;
}
const userId = userIdTag[1];
const requestingPubkey = event.pubkey;
const grantRepo = getRepository(ManagementGrant);
const grant = await grantRepo.findOne({ where: { user_id: userId, app_pubkey: appPubkey } });
if (!grant) {
console.error(`No management grant found for app ${appPubkey} and user ${userId}`);
this.sendErrorResponse(requestingPubkey, `No management grant found for app`, app);
return;
}
if (grant.expires_at && grant.expires_at.getTime() < Date.now()) {
console.error(`Management grant for app ${appPubkey} and user ${userId} has expired`);
this.sendErrorResponse(requestingPubkey, `Management grant has expired`, app);
return;
}
// Perform action
const actionTag = event.tags.find((t: string[]) => t[0] === 'a');
if (!actionTag) {
console.error("No action specified in event");
return;
}
const action = actionTag[1];
const offerRepo = getRepository(UserOffer);
switch (action) {
case "create":
const createDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
if (!createDetailsTag || !createDetailsTag[1]) {
console.error("No details provided for create action");
return;
}
try {
const offerData = JSON.parse(createDetailsTag[1]);
const newOffer = offerRepo.create({
...offerData,
user_id: userId,
managing_app_pubkey: appPubkey
});
await offerRepo.save(newOffer);
this.sendSuccessResponse(requestingPubkey, "Offer created successfully", app);
} catch (e) {
console.error("Failed to parse or save offer data", e);
this.sendErrorResponse(requestingPubkey, "Failed to create offer", app);
}
break;
case "update":
const updateTags = event.tags.filter((t: string[]) => t[0] === 'd');
if (updateTags.length < 2) {
console.error("Insufficient details for update action");
return;
}
const offerIdToUpdate = updateTags[0][1];
const updateData = JSON.parse(updateTags[1][1]);
try {
const existingOffer = await offerRepo.findOne({where: { offer_id: offerIdToUpdate }});
if (!existingOffer) {
console.error(`Offer ${offerIdToUpdate} not found`);
return;
}
if (existingOffer.managing_app_pubkey !== appPubkey) {
console.error(`App ${appPubkey} not authorized to update offer ${offerIdToUpdate}`);
return;
}
offerRepo.merge(existingOffer, updateData);
await offerRepo.save(existingOffer);
this.sendSuccessResponse(requestingPubkey, "Offer updated successfully", app);
} catch (e) {
console.error("Failed to update offer data", e);
this.sendErrorResponse(requestingPubkey, "Failed to update offer", app);
}
break;
case "delete":
const deleteDetailsTag = event.tags.find((t: string[]) => t[0] === 'd');
if (!deleteDetailsTag || !deleteDetailsTag[1]) {
console.error("No details provided for delete action");
return;
}
const offerIdToDelete = deleteDetailsTag[1];
try {
const offerToDelete = await offerRepo.findOne({where: { offer_id: offerIdToDelete }});
if (!offerToDelete) {
console.error(`Offer ${offerIdToDelete} not found`);
return;
}
if (offerToDelete.managing_app_pubkey !== appPubkey) {
console.error(`App ${appPubkey} not authorized to delete offer ${offerIdToDelete}`);
return;
}
await offerRepo.remove(offerToDelete);
this.sendSuccessResponse(requestingPubkey, "Offer deleted successfully", app);
} catch (e) {
console.error("Failed to delete offer", e);
this.sendErrorResponse(requestingPubkey, "Failed to delete offer", app);
}
break;
default:
console.error(`Unknown action: ${action}`);
this.sendErrorResponse(requestingPubkey, `Unknown action: ${action}`, app);
return;
}
}
private sendSuccessResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
const responseEvent = {
kind: 21003,
pubkey: app.publicKey,
created_at: Math.floor(Date.now() / 1000),
content: JSON.stringify({ status: "success", message }),
tags: [['p', recipient]],
};
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
}
private sendErrorResponse(recipient: string, message: string, app: {publicKey: string, appId: string}) {
const responseEvent = {
kind: 21003,
pubkey: app.publicKey,
created_at: Math.floor(Date.now() / 1000),
content: JSON.stringify({ status: "error", message }),
tags: [['p', recipient]],
};
this.nostrSend({ type: 'app', appId: app.appId }, { type: 'event', event: responseEvent, encrypt: { toPub: recipient } });
}
}

View file

@ -10,6 +10,7 @@ import EventsLogManager from "./eventsLog.js";
import { LiquidityStorage } from "./liquidityStorage.js"; import { LiquidityStorage } from "./liquidityStorage.js";
import DebitStorage from "./debitStorage.js" import DebitStorage from "./debitStorage.js"
import OfferStorage from "./offerStorage.js" import OfferStorage from "./offerStorage.js"
import { ManagementStorage } from "./managementStorage.js";
import { StorageInterface, TX } from "./db/storageInterface.js"; import { StorageInterface, TX } from "./db/storageInterface.js";
import { PubLogger } from "../helpers/logger.js" import { PubLogger } from "../helpers/logger.js"
import { TlvStorageFactory } from './tlv/tlvFilesStorageFactory.js'; import { TlvStorageFactory } from './tlv/tlvFilesStorageFactory.js';
@ -36,6 +37,7 @@ export default class {
liquidityStorage: LiquidityStorage liquidityStorage: LiquidityStorage
debitStorage: DebitStorage debitStorage: DebitStorage
offerStorage: OfferStorage offerStorage: OfferStorage
managementStorage: ManagementStorage
eventsLog: EventsLogManager eventsLog: EventsLogManager
utils: Utils utils: Utils
constructor(settings: StorageSettings, utils: Utils) { constructor(settings: StorageSettings, utils: Utils) {
@ -58,6 +60,7 @@ export default class {
this.liquidityStorage = new LiquidityStorage(this.dbs) this.liquidityStorage = new LiquidityStorage(this.dbs)
this.debitStorage = new DebitStorage(this.dbs) this.debitStorage = new DebitStorage(this.dbs)
this.offerStorage = new OfferStorage(this.dbs) this.offerStorage = new OfferStorage(this.dbs)
this.managementStorage = new ManagementStorage(this.dbs);
try { if (this.settings.dataDir) fs.mkdirSync(this.settings.dataDir) } catch (e) { } try { if (this.settings.dataDir) fs.mkdirSync(this.settings.dataDir) } catch (e) { }
/* const executedMetricsMigrations = */ await this.metricsStorage.Connect() /* const executedMetricsMigrations = */ await this.metricsStorage.Connect()
/* if (executedMigrations.length > 0) { /* if (executedMigrations.length > 0) {

View file

@ -0,0 +1,13 @@
import { StorageInterface } from "./db/storageInterface.js";
import { ManagementGrant } from "./entity/ManagementGrant.js";
export class ManagementStorage {
private dbs: StorageInterface;
constructor(dbs: StorageInterface) {
this.dbs = dbs;
}
getGrant(user_id: string, app_pubkey: string) {
return this.dbs.FindOne<ManagementGrant>('ManagementGrant' as any, { where: { user_id, app_pubkey } });
}
}