Merge branch 'signer-middleware' into 'main'
Add a signerMiddleware, c.get('pubkey') -> c.get('signer') See merge request soapbox-pub/ditto!251
This commit is contained in:
commit
a3597edb90
103
src/app.ts
103
src/app.ts
|
@ -1,9 +1,8 @@
|
|||
import { NostrEvent, NStore } from '@nostrify/nostrify';
|
||||
import { NostrEvent, NostrSigner, NStore } from '@nostrify/nostrify';
|
||||
import Debug from '@soapbox/stickynotes/debug';
|
||||
import { type Context, Env as HonoEnv, type Handler, Hono, Input as HonoInput, type MiddlewareHandler } from 'hono';
|
||||
import { cors, logger, serveStatic } from 'hono/middleware';
|
||||
|
||||
import { type User } from '@/db/users.ts';
|
||||
import '@/firehose.ts';
|
||||
import { Time } from '@/utils.ts';
|
||||
|
||||
|
@ -29,6 +28,7 @@ import { adminAccountAction, adminAccountsController } from '@/controllers/api/a
|
|||
import { appCredentialsController, createAppController } from '@/controllers/api/apps.ts';
|
||||
import { blocksController } from '@/controllers/api/blocks.ts';
|
||||
import { bookmarksController } from '@/controllers/api/bookmarks.ts';
|
||||
import { adminRelaysController, adminSetRelaysController } from '@/controllers/api/ditto.ts';
|
||||
import { emptyArrayController, emptyObjectController, notImplementedController } from '@/controllers/api/fallback.ts';
|
||||
import { instanceController } from '@/controllers/api/instance.ts';
|
||||
import { markersController, updateMarkersController } from '@/controllers/api/markers.ts';
|
||||
|
@ -80,25 +80,21 @@ import { hostMetaController } from '@/controllers/well-known/host-meta.ts';
|
|||
import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
|
||||
import { nostrController } from '@/controllers/well-known/nostr.ts';
|
||||
import { webfingerController } from '@/controllers/well-known/webfinger.ts';
|
||||
import { auth19, requirePubkey } from '@/middleware/auth19.ts';
|
||||
import { auth98, requireProof, requireRole } from '@/middleware/auth98.ts';
|
||||
import { cache } from '@/middleware/cache.ts';
|
||||
import { csp } from '@/middleware/csp.ts';
|
||||
import { adminRelaysController, adminSetRelaysController } from '@/controllers/api/ditto.ts';
|
||||
import { storeMiddleware } from '@/middleware/store.ts';
|
||||
import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts';
|
||||
import { cacheMiddleware } from '@/middleware/cacheMiddleware.ts';
|
||||
import { cspMiddleware } from '@/middleware/cspMiddleware.ts';
|
||||
import { requireSigner } from '@/middleware/requireSigner.ts';
|
||||
import { signerMiddleware } from '@/middleware/signerMiddleware.ts';
|
||||
import { storeMiddleware } from '@/middleware/storeMiddleware.ts';
|
||||
import { blockController } from '@/controllers/api/accounts.ts';
|
||||
import { unblockController } from '@/controllers/api/accounts.ts';
|
||||
|
||||
interface AppEnv extends HonoEnv {
|
||||
Variables: {
|
||||
/** Hex pubkey for the current user. If provided, the user is considered "logged in." */
|
||||
pubkey?: string;
|
||||
/** Hex secret key for the current user. Optional, but easiest way to use legacy Mastodon apps. */
|
||||
seckey?: Uint8Array;
|
||||
/** Signer to get the logged-in user's pubkey, relays, and to sign events, or `undefined` if the user isn't logged in. */
|
||||
signer?: NostrSigner;
|
||||
/** NIP-98 signed event proving the pubkey is owned by the user. */
|
||||
proof?: NostrEvent;
|
||||
/** User associated with the pubkey, if any. */
|
||||
user?: User;
|
||||
/** Store */
|
||||
store: NStore;
|
||||
};
|
||||
|
@ -123,7 +119,14 @@ app.get('/api/v1/streaming', streamingController);
|
|||
app.get('/api/v1/streaming/', streamingController);
|
||||
app.get('/relay', relayController);
|
||||
|
||||
app.use('*', csp(), cors({ origin: '*', exposeHeaders: ['link'] }), auth19, auth98(), storeMiddleware);
|
||||
app.use(
|
||||
'*',
|
||||
cspMiddleware(),
|
||||
cors({ origin: '*', exposeHeaders: ['link'] }),
|
||||
signerMiddleware,
|
||||
auth98Middleware(),
|
||||
storeMiddleware,
|
||||
);
|
||||
|
||||
app.get('/.well-known/webfinger', webfingerController);
|
||||
app.get('/.well-known/host-meta', hostMetaController);
|
||||
|
@ -134,7 +137,7 @@ app.get('/users/:username', actorController);
|
|||
|
||||
app.get('/nodeinfo/:version', nodeInfoSchemaController);
|
||||
|
||||
app.get('/api/v1/instance', cache({ cacheName: 'web', expires: Time.minutes(5) }), instanceController);
|
||||
app.get('/api/v1/instance', cacheMiddleware({ cacheName: 'web', expires: Time.minutes(5) }), instanceController);
|
||||
|
||||
app.get('/api/v1/apps/verify_credentials', appCredentialsController);
|
||||
app.post('/api/v1/apps', createAppController);
|
||||
|
@ -145,17 +148,17 @@ app.post('/oauth/authorize', oauthAuthorizeController);
|
|||
app.get('/oauth/authorize', oauthController);
|
||||
|
||||
app.post('/api/v1/accounts', requireProof({ pow: 20 }), createAccountController);
|
||||
app.get('/api/v1/accounts/verify_credentials', requirePubkey, verifyCredentialsController);
|
||||
app.patch('/api/v1/accounts/update_credentials', requirePubkey, updateCredentialsController);
|
||||
app.get('/api/v1/accounts/verify_credentials', requireSigner, verifyCredentialsController);
|
||||
app.patch('/api/v1/accounts/update_credentials', requireSigner, updateCredentialsController);
|
||||
app.get('/api/v1/accounts/search', accountSearchController);
|
||||
app.get('/api/v1/accounts/lookup', accountLookupController);
|
||||
app.get('/api/v1/accounts/relationships', requirePubkey, relationshipsController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/block', requirePubkey, blockController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unblock', requirePubkey, unblockController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/mute', requirePubkey, muteController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unmute', requirePubkey, unmuteController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/follow', requirePubkey, followController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unfollow', requirePubkey, unfollowController);
|
||||
app.get('/api/v1/accounts/relationships', requireSigner, relationshipsController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/block', requireSigner, blockController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unblock', requireSigner, unblockController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/mute', requireSigner, muteController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unmute', requireSigner, unmuteController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/follow', requireSigner, followController);
|
||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unfollow', requireSigner, unfollowController);
|
||||
app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/followers', followersController);
|
||||
app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/following', followingController);
|
||||
app.get('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/statuses', accountStatusesController);
|
||||
|
@ -165,21 +168,21 @@ app.get('/api/v1/statuses/:id{[0-9a-f]{64}}/favourited_by', favouritedByControll
|
|||
app.get('/api/v1/statuses/:id{[0-9a-f]{64}}/reblogged_by', rebloggedByController);
|
||||
app.get('/api/v1/statuses/:id{[0-9a-f]{64}}/context', contextController);
|
||||
app.get('/api/v1/statuses/:id{[0-9a-f]{64}}', statusController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/favourite', requirePubkey, favouriteController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/bookmark', requirePubkey, bookmarkController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', requirePubkey, unbookmarkController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', requirePubkey, pinController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', requirePubkey, unpinController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/zap', requirePubkey, zapController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/reblog', requirePubkey, reblogStatusController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unreblog', requirePubkey, unreblogStatusController);
|
||||
app.post('/api/v1/statuses', requirePubkey, createStatusController);
|
||||
app.delete('/api/v1/statuses/:id{[0-9a-f]{64}}', requirePubkey, deleteStatusController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/favourite', requireSigner, favouriteController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/bookmark', requireSigner, bookmarkController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', requireSigner, unbookmarkController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', requireSigner, pinController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', requireSigner, unpinController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/zap', requireSigner, zapController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/reblog', requireSigner, reblogStatusController);
|
||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unreblog', requireSigner, unreblogStatusController);
|
||||
app.post('/api/v1/statuses', requireSigner, createStatusController);
|
||||
app.delete('/api/v1/statuses/:id{[0-9a-f]{64}}', requireSigner, deleteStatusController);
|
||||
|
||||
app.post('/api/v1/media', mediaController);
|
||||
app.post('/api/v2/media', mediaController);
|
||||
|
||||
app.get('/api/v1/timelines/home', requirePubkey, homeTimelineController);
|
||||
app.get('/api/v1/timelines/home', requireSigner, homeTimelineController);
|
||||
app.get('/api/v1/timelines/public', publicTimelineController);
|
||||
app.get('/api/v1/timelines/tag/:hashtag', hashtagTimelineController);
|
||||
|
||||
|
@ -189,17 +192,21 @@ app.get('/api/v2/search', searchController);
|
|||
|
||||
app.get('/api/pleroma/frontend_configurations', frontendConfigController);
|
||||
|
||||
app.get('/api/v1/trends/tags', cache({ cacheName: 'web', expires: Time.minutes(15) }), trendingTagsController);
|
||||
app.get('/api/v1/trends', cache({ cacheName: 'web', expires: Time.minutes(15) }), trendingTagsController);
|
||||
app.get(
|
||||
'/api/v1/trends/tags',
|
||||
cacheMiddleware({ cacheName: 'web', expires: Time.minutes(15) }),
|
||||
trendingTagsController,
|
||||
);
|
||||
app.get('/api/v1/trends', cacheMiddleware({ cacheName: 'web', expires: Time.minutes(15) }), trendingTagsController);
|
||||
|
||||
app.get('/api/v1/suggestions', suggestionsV1Controller);
|
||||
app.get('/api/v2/suggestions', suggestionsV2Controller);
|
||||
|
||||
app.get('/api/v1/notifications', requirePubkey, notificationsController);
|
||||
app.get('/api/v1/favourites', requirePubkey, favouritesController);
|
||||
app.get('/api/v1/bookmarks', requirePubkey, bookmarksController);
|
||||
app.get('/api/v1/blocks', requirePubkey, blocksController);
|
||||
app.get('/api/v1/mutes', requirePubkey, mutesController);
|
||||
app.get('/api/v1/notifications', requireSigner, notificationsController);
|
||||
app.get('/api/v1/favourites', requireSigner, favouritesController);
|
||||
app.get('/api/v1/bookmarks', requireSigner, bookmarksController);
|
||||
app.get('/api/v1/blocks', requireSigner, blocksController);
|
||||
app.get('/api/v1/mutes', requireSigner, mutesController);
|
||||
|
||||
app.get('/api/v1/markers', requireProof(), markersController);
|
||||
app.post('/api/v1/markers', requireProof(), updateMarkersController);
|
||||
|
@ -212,17 +219,17 @@ app.delete('/api/v1/pleroma/admin/statuses/:id', requireRole('admin'), pleromaAd
|
|||
app.get('/api/v1/admin/ditto/relays', requireRole('admin'), adminRelaysController);
|
||||
app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysController);
|
||||
|
||||
app.post('/api/v1/reports', requirePubkey, reportController);
|
||||
app.get('/api/v1/admin/reports', requirePubkey, requireRole('admin'), adminReportsController);
|
||||
app.get('/api/v1/admin/reports/:id{[0-9a-f]{64}}', requirePubkey, requireRole('admin'), adminReportController);
|
||||
app.post('/api/v1/reports', requireSigner, reportController);
|
||||
app.get('/api/v1/admin/reports', requireSigner, requireRole('admin'), adminReportsController);
|
||||
app.get('/api/v1/admin/reports/:id{[0-9a-f]{64}}', requireSigner, requireRole('admin'), adminReportController);
|
||||
app.post(
|
||||
'/api/v1/admin/reports/:id{[0-9a-f]{64}}/resolve',
|
||||
requirePubkey,
|
||||
requireSigner,
|
||||
requireRole('admin'),
|
||||
adminReportResolveController,
|
||||
);
|
||||
|
||||
app.post('/api/v1/admin/accounts/:id{[0-9a-f]{64}}/action', requirePubkey, requireRole('admin'), adminAccountAction);
|
||||
app.post('/api/v1/admin/accounts/:id{[0-9a-f]{64}}/action', requireSigner, requireRole('admin'), adminAccountAction);
|
||||
|
||||
// Not (yet) implemented.
|
||||
app.get('/api/v1/custom_emojis', emptyArrayController);
|
||||
|
|
|
@ -29,7 +29,7 @@ const createAccountSchema = z.object({
|
|||
});
|
||||
|
||||
const createAccountController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const result = createAccountSchema.safeParse(await c.req.json());
|
||||
|
||||
if (!result.success) {
|
||||
|
@ -45,7 +45,7 @@ const createAccountController: AppController = async (c) => {
|
|||
};
|
||||
|
||||
const verifyCredentialsController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
|
||||
const event = await getAuthor(pubkey, { relations: ['author_stats'] });
|
||||
if (event) {
|
||||
|
@ -122,7 +122,7 @@ const accountSearchController: AppController = async (c) => {
|
|||
};
|
||||
|
||||
const relationshipsController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const ids = z.array(z.string()).safeParse(c.req.queries('id[]'));
|
||||
|
||||
if (!ids.success) {
|
||||
|
@ -178,7 +178,11 @@ const accountStatusesController: AppController = async (c) => {
|
|||
return events;
|
||||
});
|
||||
|
||||
const statuses = await Promise.all(events.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
|
||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const statuses = await Promise.all(
|
||||
events.map((event) => renderStatus(event, { viewerPubkey })),
|
||||
);
|
||||
return paginated(c, events, statuses);
|
||||
};
|
||||
|
||||
|
@ -194,7 +198,7 @@ const updateCredentialsSchema = z.object({
|
|||
});
|
||||
|
||||
const updateCredentialsController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const body = await parseBody(c.req.raw);
|
||||
const result = updateCredentialsSchema.safeParse(body);
|
||||
|
||||
|
@ -236,7 +240,7 @@ const updateCredentialsController: AppController = async (c) => {
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/accounts/#follow */
|
||||
const followController: AppController = async (c) => {
|
||||
const sourcePubkey = c.get('pubkey')!;
|
||||
const sourcePubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const targetPubkey = c.req.param('pubkey');
|
||||
|
||||
await updateListEvent(
|
||||
|
@ -253,7 +257,7 @@ const followController: AppController = async (c) => {
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/accounts/#unfollow */
|
||||
const unfollowController: AppController = async (c) => {
|
||||
const sourcePubkey = c.get('pubkey')!;
|
||||
const sourcePubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const targetPubkey = c.req.param('pubkey');
|
||||
|
||||
await updateListEvent(
|
||||
|
@ -290,7 +294,7 @@ const unblockController: AppController = (c) => {
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/accounts/#mute */
|
||||
const muteController: AppController = async (c) => {
|
||||
const sourcePubkey = c.get('pubkey')!;
|
||||
const sourcePubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const targetPubkey = c.req.param('pubkey');
|
||||
|
||||
await updateListEvent(
|
||||
|
@ -305,7 +309,7 @@ const muteController: AppController = async (c) => {
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/accounts/#unmute */
|
||||
const unmuteController: AppController = async (c) => {
|
||||
const sourcePubkey = c.get('pubkey')!;
|
||||
const sourcePubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const targetPubkey = c.req.param('pubkey');
|
||||
|
||||
await updateListEvent(
|
||||
|
@ -319,7 +323,7 @@ const unmuteController: AppController = async (c) => {
|
|||
};
|
||||
|
||||
const favouritesController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const params = paginationSchema.parse(c.req.query());
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
|
@ -335,7 +339,11 @@ const favouritesController: AppController = async (c) => {
|
|||
const events1 = await Storages.db.query([{ kinds: [1], ids }], { signal })
|
||||
.then((events) => hydrateEvents({ events, storage: Storages.db, signal }));
|
||||
|
||||
const statuses = await Promise.all(events1.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
|
||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const statuses = await Promise.all(
|
||||
events1.map((event) => renderStatus(event, { viewerPubkey })),
|
||||
);
|
||||
return paginated(c, events1, statuses);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { renderStatuses } from '@/views.ts';
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/bookmarks/#get */
|
||||
const bookmarksController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
const [event10003] = await Storages.db.query(
|
||||
|
|
|
@ -14,7 +14,7 @@ interface Marker {
|
|||
}
|
||||
|
||||
export const markersController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const timelines = c.req.queries('timeline[]') ?? [];
|
||||
|
||||
const results = await kv.getMany<Marker[]>(
|
||||
|
@ -37,7 +37,7 @@ const markerDataSchema = z.object({
|
|||
});
|
||||
|
||||
export const updateMarkersController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const record = z.record(z.enum(['home', 'notifications']), markerDataSchema).parse(await parseBody(c.req.raw));
|
||||
const timelines = Object.keys(record) as Timeline[];
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ const mediaBodySchema = z.object({
|
|||
});
|
||||
|
||||
const mediaController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const result = mediaBodySchema.safeParse(await parseBody(c.req.raw));
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { renderAccounts } from '@/views.ts';
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/mutes/#get */
|
||||
const mutesController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
const [event10000] = await Storages.db.query(
|
||||
|
|
|
@ -5,8 +5,8 @@ import { hydrateEvents } from '@/storages/hydrate.ts';
|
|||
import { paginated, paginationSchema } from '@/utils/api.ts';
|
||||
import { renderNotification } from '@/views/mastodon/notifications.ts';
|
||||
|
||||
const notificationsController: AppController = (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const notificationsController: AppController = async (c) => {
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const { since, until } = paginationSchema.parse(c.req.query());
|
||||
|
||||
return renderNotifications(c, [{ kinds: [1, 6, 7], '#p': [pubkey], since, until }]);
|
||||
|
@ -14,7 +14,7 @@ const notificationsController: AppController = (c) => {
|
|||
|
||||
async function renderNotifications(c: AppContext, filters: NostrFilter[]) {
|
||||
const store = c.get('store');
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
const events = await store
|
||||
|
|
|
@ -55,9 +55,15 @@ const reportController: AppController = async (c) => {
|
|||
/** https://docs.joinmastodon.org/methods/admin/reports/#get */
|
||||
const adminReportsController: AppController = async (c) => {
|
||||
const store = c.get('store');
|
||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const reports = await store.query([{ kinds: [1984], '#P': [Conf.pubkey] }])
|
||||
.then((events) => hydrateEvents({ storage: store, events: events, signal: c.req.raw.signal }))
|
||||
.then((events) => Promise.all(events.map((event) => renderAdminReport(event, { viewerPubkey: c.get('pubkey') }))));
|
||||
.then((events) =>
|
||||
Promise.all(
|
||||
events.map((event) => renderAdminReport(event, { viewerPubkey })),
|
||||
)
|
||||
);
|
||||
|
||||
return c.json(reports);
|
||||
};
|
||||
|
@ -67,7 +73,7 @@ const adminReportController: AppController = async (c) => {
|
|||
const eventId = c.req.param('id');
|
||||
const { signal } = c.req.raw;
|
||||
const store = c.get('store');
|
||||
const pubkey = c.get('pubkey');
|
||||
const pubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const [event] = await store.query([{
|
||||
kinds: [1984],
|
||||
|
@ -89,7 +95,7 @@ const adminReportResolveController: AppController = async (c) => {
|
|||
const eventId = c.req.param('id');
|
||||
const { signal } = c.req.raw;
|
||||
const store = c.get('store');
|
||||
const pubkey = c.get('pubkey');
|
||||
const pubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const [event] = await store.query([{
|
||||
kinds: [1984],
|
||||
|
|
|
@ -43,6 +43,7 @@ const searchController: AppController = async (c) => {
|
|||
}
|
||||
|
||||
const results = dedupeEvents(events);
|
||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const [accounts, statuses] = await Promise.all([
|
||||
Promise.all(
|
||||
|
@ -54,7 +55,7 @@ const searchController: AppController = async (c) => {
|
|||
Promise.all(
|
||||
results
|
||||
.filter((event) => event.kind === 1)
|
||||
.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') }))
|
||||
.map((event) => renderStatus(event, { viewerPubkey }))
|
||||
.filter(Boolean),
|
||||
),
|
||||
]);
|
||||
|
|
|
@ -47,7 +47,7 @@ const statusController: AppController = async (c) => {
|
|||
});
|
||||
|
||||
if (event) {
|
||||
return c.json(await renderStatus(event, { viewerPubkey: c.get('pubkey') }));
|
||||
return c.json(await renderStatus(event, { viewerPubkey: await c.get('signer')?.getPublicKey() }));
|
||||
}
|
||||
|
||||
return c.json({ error: 'Event not found.' }, 404);
|
||||
|
@ -89,9 +89,11 @@ const createStatusController: AppController = async (c) => {
|
|||
tags.push(['subject', data.spoiler_text]);
|
||||
}
|
||||
|
||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
if (data.media_ids?.length) {
|
||||
const media = await getUnattachedMediaByIds(data.media_ids)
|
||||
.then((media) => media.filter(({ pubkey }) => pubkey === c.get('pubkey')))
|
||||
.then((media) => media.filter(({ pubkey }) => pubkey === viewerPubkey))
|
||||
.then((media) => media.map(({ url, data }) => ['media', url, data]));
|
||||
|
||||
tags.push(...media);
|
||||
|
@ -143,12 +145,12 @@ const createStatusController: AppController = async (c) => {
|
|||
});
|
||||
}
|
||||
|
||||
return c.json(await renderStatus({ ...event, author }, { viewerPubkey: c.get('pubkey') }));
|
||||
return c.json(await renderStatus({ ...event, author }, { viewerPubkey: await c.get('signer')?.getPublicKey() }));
|
||||
};
|
||||
|
||||
const deleteStatusController: AppController = async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const pubkey = c.get('pubkey');
|
||||
const pubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const event = await getEvent(id, { signal: c.req.raw.signal });
|
||||
|
||||
|
@ -172,9 +174,12 @@ const deleteStatusController: AppController = async (c) => {
|
|||
const contextController: AppController = async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const event = await getEvent(id, { kind: 1, relations: ['author', 'event_stats', 'author_stats'] });
|
||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
async function renderStatuses(events: NostrEvent[]) {
|
||||
const statuses = await Promise.all(events.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })));
|
||||
const statuses = await Promise.all(
|
||||
events.map((event) => renderStatus(event, { viewerPubkey })),
|
||||
);
|
||||
return statuses.filter(Boolean);
|
||||
}
|
||||
|
||||
|
@ -204,7 +209,7 @@ const favouriteController: AppController = async (c) => {
|
|||
],
|
||||
}, c);
|
||||
|
||||
const status = await renderStatus(target, { viewerPubkey: c.get('pubkey') });
|
||||
const status = await renderStatus(target, { viewerPubkey: await c.get('signer')?.getPublicKey() });
|
||||
|
||||
if (status) {
|
||||
status.favourited = true;
|
||||
|
@ -247,7 +252,7 @@ const reblogStatusController: AppController = async (c) => {
|
|||
signal: signal,
|
||||
});
|
||||
|
||||
const status = await renderReblog(reblogEvent, { viewerPubkey: c.get('pubkey') });
|
||||
const status = await renderReblog(reblogEvent, { viewerPubkey: await c.get('signer')?.getPublicKey() });
|
||||
|
||||
return c.json(status);
|
||||
};
|
||||
|
@ -255,7 +260,7 @@ const reblogStatusController: AppController = async (c) => {
|
|||
/** https://docs.joinmastodon.org/methods/statuses/#unreblog */
|
||||
const unreblogStatusController: AppController = async (c) => {
|
||||
const eventId = c.req.param('id');
|
||||
const pubkey = c.get('pubkey') as string;
|
||||
const pubkey = await c.get('signer')?.getPublicKey() as string;
|
||||
|
||||
const event = await getEvent(eventId, {
|
||||
kind: 1,
|
||||
|
@ -282,7 +287,7 @@ const rebloggedByController: AppController = (c) => {
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/statuses/#bookmark */
|
||||
const bookmarkController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const eventId = c.req.param('id');
|
||||
|
||||
const event = await getEvent(eventId, {
|
||||
|
@ -309,7 +314,7 @@ const bookmarkController: AppController = async (c) => {
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/statuses/#unbookmark */
|
||||
const unbookmarkController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const eventId = c.req.param('id');
|
||||
|
||||
const event = await getEvent(eventId, {
|
||||
|
@ -336,7 +341,7 @@ const unbookmarkController: AppController = async (c) => {
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/statuses/#pin */
|
||||
const pinController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const eventId = c.req.param('id');
|
||||
|
||||
const event = await getEvent(eventId, {
|
||||
|
@ -363,7 +368,7 @@ const pinController: AppController = async (c) => {
|
|||
|
||||
/** https://docs.joinmastodon.org/methods/statuses/#unpin */
|
||||
const unpinController: AppController = async (c) => {
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const eventId = c.req.param('id');
|
||||
const { signal } = c.req.raw;
|
||||
|
||||
|
@ -423,7 +428,7 @@ const zapController: AppController = async (c) => {
|
|||
],
|
||||
}, c);
|
||||
|
||||
const status = await renderStatus(target, { viewerPubkey: c.get('pubkey') });
|
||||
const status = await renderStatus(target, { viewerPubkey: await c.get('signer')?.getPublicKey() });
|
||||
status.zapped = true;
|
||||
|
||||
return c.json(status);
|
||||
|
|
|
@ -11,7 +11,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts';
|
|||
|
||||
const homeTimelineController: AppController = async (c) => {
|
||||
const params = paginationSchema.parse(c.req.query());
|
||||
const pubkey = c.get('pubkey')!;
|
||||
const pubkey = await c.get('signer')?.getPublicKey()!;
|
||||
const authors = await getFeedPubkeys(pubkey);
|
||||
return renderStatuses(c, [{ authors, kinds: [1, 6], ...params }]);
|
||||
};
|
||||
|
@ -61,11 +61,13 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) {
|
|||
return c.json([]);
|
||||
}
|
||||
|
||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const statuses = (await Promise.all(events.map((event) => {
|
||||
if (event.kind === 6) {
|
||||
return renderReblog(event, { viewerPubkey: c.get('pubkey') });
|
||||
return renderReblog(event, { viewerPubkey });
|
||||
}
|
||||
return renderStatus(event, { viewerPubkey: c.get('pubkey') });
|
||||
return renderStatus(event, { viewerPubkey });
|
||||
}))).filter((boolean) => boolean);
|
||||
|
||||
if (!statuses.length) {
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import { HTTPException } from 'hono';
|
||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||
|
||||
import { type AppMiddleware } from '@/app.ts';
|
||||
|
||||
/** We only accept "Bearer" type. */
|
||||
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
|
||||
|
||||
/** NIP-19 auth middleware. */
|
||||
const auth19: AppMiddleware = async (c, next) => {
|
||||
const authHeader = c.req.header('authorization');
|
||||
const match = authHeader?.match(BEARER_REGEX);
|
||||
|
||||
if (match) {
|
||||
const [_, bech32] = match;
|
||||
|
||||
try {
|
||||
const decoded = nip19.decode(bech32!);
|
||||
|
||||
switch (decoded.type) {
|
||||
case 'npub':
|
||||
c.set('pubkey', decoded.data);
|
||||
break;
|
||||
case 'nprofile':
|
||||
c.set('pubkey', decoded.data.pubkey);
|
||||
break;
|
||||
case 'nsec':
|
||||
c.set('pubkey', getPublicKey(decoded.data));
|
||||
c.set('seckey', decoded.data);
|
||||
break;
|
||||
}
|
||||
} catch (_e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
/** Throw a 401 if the pubkey isn't set. */
|
||||
const requirePubkey: AppMiddleware = async (c, next) => {
|
||||
if (!c.get('pubkey')) {
|
||||
throw new HTTPException(401, { message: 'No pubkey provided' });
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
export { auth19, requirePubkey };
|
|
@ -1,27 +1,28 @@
|
|||
import { NostrEvent } from '@nostrify/nostrify';
|
||||
import { HTTPException } from 'hono';
|
||||
|
||||
import { type AppContext, type AppMiddleware } from '@/app.ts';
|
||||
import { findUser, User } from '@/db/users.ts';
|
||||
import { ConnectSigner } from '@/signers/ConnectSigner.ts';
|
||||
import { localRequest } from '@/utils/api.ts';
|
||||
import {
|
||||
buildAuthEventTemplate,
|
||||
parseAuthRequest,
|
||||
type ParseAuthRequestOpts,
|
||||
validateAuthEvent,
|
||||
} from '@/utils/nip98.ts';
|
||||
import { localRequest } from '@/utils/api.ts';
|
||||
import { APISigner } from '@/signers/APISigner.ts';
|
||||
import { findUser, User } from '@/db/users.ts';
|
||||
|
||||
/**
|
||||
* NIP-98 auth.
|
||||
* https://github.com/nostr-protocol/nips/blob/master/98.md
|
||||
*/
|
||||
function auth98(opts: ParseAuthRequestOpts = {}): AppMiddleware {
|
||||
function auth98Middleware(opts: ParseAuthRequestOpts = {}): AppMiddleware {
|
||||
return async (c, next) => {
|
||||
const req = localRequest(c);
|
||||
const result = await parseAuthRequest(req, opts);
|
||||
|
||||
if (result.success) {
|
||||
c.set('pubkey', result.data.pubkey);
|
||||
c.set('signer', new ConnectSigner(result.data.pubkey));
|
||||
c.set('proof', result.data);
|
||||
}
|
||||
|
||||
|
@ -33,9 +34,8 @@ type UserRole = 'user' | 'admin';
|
|||
|
||||
/** Require the user to prove their role before invoking the controller. */
|
||||
function requireRole(role: UserRole, opts?: ParseAuthRequestOpts): AppMiddleware {
|
||||
return withProof(async (c, proof, next) => {
|
||||
return withProof(async (_c, proof, next) => {
|
||||
const user = await findUser({ pubkey: proof.pubkey });
|
||||
c.set('user', user);
|
||||
|
||||
if (user && matchesRole(user, role)) {
|
||||
await next();
|
||||
|
@ -70,7 +70,7 @@ function withProof(
|
|||
opts?: ParseAuthRequestOpts,
|
||||
): AppMiddleware {
|
||||
return async (c, next) => {
|
||||
const pubkey = c.get('pubkey');
|
||||
const pubkey = await c.get('signer')?.getPublicKey();
|
||||
const proof = c.get('proof') || await obtainProof(c, opts);
|
||||
|
||||
// Prevent people from accidentally using the wrong account. This has no other security implications.
|
||||
|
@ -79,7 +79,7 @@ function withProof(
|
|||
}
|
||||
|
||||
if (proof) {
|
||||
c.set('pubkey', proof.pubkey);
|
||||
c.set('signer', new ConnectSigner(proof.pubkey));
|
||||
c.set('proof', proof);
|
||||
await handler(c, proof, next);
|
||||
} else {
|
||||
|
@ -90,9 +90,16 @@ function withProof(
|
|||
|
||||
/** Get the proof over Nostr Connect. */
|
||||
async function obtainProof(c: AppContext, opts?: ParseAuthRequestOpts) {
|
||||
const signer = c.get('signer');
|
||||
if (!signer) {
|
||||
throw new HTTPException(401, {
|
||||
res: c.json({ error: 'No way to sign Nostr event' }, 401),
|
||||
});
|
||||
}
|
||||
|
||||
const req = localRequest(c);
|
||||
const reqEvent = await buildAuthEventTemplate(req, opts);
|
||||
const resEvent = await new APISigner(c).signEvent(reqEvent);
|
||||
const resEvent = await signer.signEvent(reqEvent);
|
||||
const result = await validateAuthEvent(req, resEvent, opts);
|
||||
|
||||
if (result.success) {
|
||||
|
@ -100,4 +107,4 @@ async function obtainProof(c: AppContext, opts?: ParseAuthRequestOpts) {
|
|||
}
|
||||
}
|
||||
|
||||
export { auth98, requireProof, requireRole };
|
||||
export { auth98Middleware, requireProof, requireRole };
|
|
@ -5,7 +5,7 @@ import ExpiringCache from '@/utils/expiring-cache.ts';
|
|||
|
||||
const debug = Debug('ditto:middleware:cache');
|
||||
|
||||
export const cache = (options: {
|
||||
export const cacheMiddleware = (options: {
|
||||
cacheName: string;
|
||||
expires?: number;
|
||||
}): MiddlewareHandler => {
|
|
@ -1,7 +1,7 @@
|
|||
import { AppMiddleware } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
|
||||
const csp = (): AppMiddleware => {
|
||||
export const cspMiddleware = (): AppMiddleware => {
|
||||
return async (c, next) => {
|
||||
const { host, protocol, origin } = Conf.url;
|
||||
const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:';
|
||||
|
@ -26,5 +26,3 @@ const csp = (): AppMiddleware => {
|
|||
await next();
|
||||
};
|
||||
};
|
||||
|
||||
export { csp };
|
|
@ -0,0 +1,12 @@
|
|||
import { HTTPException } from 'hono';
|
||||
|
||||
import { AppMiddleware } from '@/app.ts';
|
||||
|
||||
/** Throw a 401 if a signer isn't set. */
|
||||
export const requireSigner: AppMiddleware = async (c, next) => {
|
||||
if (!c.get('signer')) {
|
||||
throw new HTTPException(401, { message: 'No pubkey provided' });
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
import { NSecSigner } from '@nostrify/nostrify';
|
||||
import { Stickynotes } from '@soapbox/stickynotes';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
import { AppMiddleware } from '@/app.ts';
|
||||
import { ConnectSigner } from '@/signers/ConnectSigner.ts';
|
||||
|
||||
const console = new Stickynotes('ditto:signerMiddleware');
|
||||
|
||||
/** We only accept "Bearer" type. */
|
||||
const BEARER_REGEX = new RegExp(`^Bearer (${nip19.BECH32_REGEX.source})$`);
|
||||
|
||||
/** Make a `signer` object available to all controllers, or unset if the user isn't logged in. */
|
||||
export const signerMiddleware: AppMiddleware = async (c, next) => {
|
||||
const header = c.req.header('authorization');
|
||||
const match = header?.match(BEARER_REGEX);
|
||||
|
||||
if (match) {
|
||||
const [_, bech32] = match;
|
||||
|
||||
try {
|
||||
const decoded = nip19.decode(bech32!);
|
||||
|
||||
switch (decoded.type) {
|
||||
case 'npub':
|
||||
c.set('signer', new ConnectSigner(decoded.data));
|
||||
break;
|
||||
case 'nprofile':
|
||||
c.set('signer', new ConnectSigner(decoded.data.pubkey, decoded.data.relays));
|
||||
break;
|
||||
case 'nsec':
|
||||
c.set('signer', new NSecSigner(decoded.data));
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
console.debug('The user is not logged in');
|
||||
}
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
|
@ -3,8 +3,8 @@ import { UserStore } from '@/storages/UserStore.ts';
|
|||
import { Storages } from '@/storages.ts';
|
||||
|
||||
/** Store middleware. */
|
||||
const storeMiddleware: AppMiddleware = async (c, next) => {
|
||||
const pubkey = c.get('pubkey');
|
||||
export const storeMiddleware: AppMiddleware = async (c, next) => {
|
||||
const pubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
if (pubkey) {
|
||||
const store = new UserStore(pubkey, Storages.admin);
|
||||
|
@ -14,5 +14,3 @@ const storeMiddleware: AppMiddleware = async (c, next) => {
|
|||
}
|
||||
await next();
|
||||
};
|
||||
|
||||
export { storeMiddleware };
|
|
@ -1,65 +0,0 @@
|
|||
// deno-lint-ignore-file require-await
|
||||
|
||||
import { NConnectSigner, NostrEvent, NostrSigner, NSecSigner } from '@nostrify/nostrify';
|
||||
import { HTTPException } from 'hono';
|
||||
import { type AppContext } from '@/app.ts';
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
/**
|
||||
* Sign Nostr event using the app context.
|
||||
*
|
||||
* - If a secret key is provided, it will be used to sign the event.
|
||||
* - Otherwise, it will use NIP-46 to sign the event.
|
||||
*/
|
||||
export class APISigner implements NostrSigner {
|
||||
private signer: NostrSigner;
|
||||
|
||||
constructor(c: AppContext) {
|
||||
const seckey = c.get('seckey');
|
||||
const pubkey = c.get('pubkey');
|
||||
|
||||
if (!pubkey) {
|
||||
throw new HTTPException(401, { message: 'Missing pubkey' });
|
||||
}
|
||||
|
||||
if (seckey) {
|
||||
this.signer = new NSecSigner(seckey);
|
||||
} else {
|
||||
this.signer = new NConnectSigner({
|
||||
pubkey,
|
||||
relay: Storages.pubsub,
|
||||
signer: new AdminSigner(),
|
||||
timeout: 60000,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getPublicKey(): Promise<string> {
|
||||
return this.signer.getPublicKey();
|
||||
}
|
||||
|
||||
async signEvent(event: Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>): Promise<NostrEvent> {
|
||||
return this.signer.signEvent(event);
|
||||
}
|
||||
|
||||
readonly nip04 = {
|
||||
encrypt: async (pubkey: string, plaintext: string): Promise<string> => {
|
||||
return this.signer.nip04!.encrypt(pubkey, plaintext);
|
||||
},
|
||||
|
||||
decrypt: async (pubkey: string, ciphertext: string): Promise<string> => {
|
||||
return this.signer.nip04!.decrypt(pubkey, ciphertext);
|
||||
},
|
||||
};
|
||||
|
||||
readonly nip44 = {
|
||||
encrypt: async (pubkey: string, plaintext: string): Promise<string> => {
|
||||
return this.signer.nip44!.encrypt(pubkey, plaintext);
|
||||
},
|
||||
|
||||
decrypt: async (pubkey: string, ciphertext: string): Promise<string> => {
|
||||
return this.signer.nip44!.decrypt(pubkey, ciphertext);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// deno-lint-ignore-file require-await
|
||||
import { NConnectSigner } from '@nostrify/nostrify';
|
||||
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
|
||||
/**
|
||||
* NIP-46 signer.
|
||||
*
|
||||
* Simple extension of nostrify's `NConnectSigner`, with our options to keep it DRY.
|
||||
*/
|
||||
export class ConnectSigner extends NConnectSigner {
|
||||
private _pubkey: string;
|
||||
|
||||
constructor(pubkey: string, private relays?: string[]) {
|
||||
super({
|
||||
pubkey,
|
||||
// TODO: use a remote relay for `nprofile` signing (if present and `Conf.relay` isn't already in the list)
|
||||
relay: Storages.pubsub,
|
||||
signer: new AdminSigner(),
|
||||
timeout: 60000,
|
||||
});
|
||||
|
||||
this._pubkey = pubkey;
|
||||
}
|
||||
|
||||
// Prevent unnecessary NIP-46 round-trips.
|
||||
async getPublicKey(): Promise<string> {
|
||||
return this._pubkey;
|
||||
}
|
||||
|
||||
/** Get the user's relays if they passed in an `nprofile` auth token. */
|
||||
async getRelays(): Promise<Record<string, { read: boolean; write: boolean }>> {
|
||||
return this.relays?.reduce<Record<string, { read: boolean; write: boolean }>>((acc, relay) => {
|
||||
acc[relay] = { read: true, write: true };
|
||||
return acc;
|
||||
}, {}) ?? {};
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ import { Conf } from '@/config.ts';
|
|||
import * as pipeline from '@/pipeline.ts';
|
||||
import { RelayError } from '@/RelayError.ts';
|
||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||
import { APISigner } from '@/signers/APISigner.ts';
|
||||
import { Storages } from '@/storages.ts';
|
||||
import { nostrNow } from '@/utils.ts';
|
||||
|
||||
|
@ -22,7 +21,13 @@ type EventStub = TypeFest.SetOptional<EventTemplate, 'content' | 'created_at' |
|
|||
|
||||
/** Publish an event through the pipeline. */
|
||||
async function createEvent(t: EventStub, c: AppContext): Promise<NostrEvent> {
|
||||
const signer = new APISigner(c);
|
||||
const signer = c.get('signer');
|
||||
|
||||
if (!signer) {
|
||||
throw new HTTPException(401, {
|
||||
res: c.json({ error: 'No way to sign Nostr event' }, 401),
|
||||
});
|
||||
}
|
||||
|
||||
const event = await signer.signEvent({
|
||||
content: '',
|
||||
|
|
|
@ -59,8 +59,10 @@ async function renderStatuses(c: AppContext, ids: string[], signal = AbortSignal
|
|||
|
||||
const sortedEvents = [...events].sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
|
||||
|
||||
const viewerPubkey = await c.get('signer')?.getPublicKey();
|
||||
|
||||
const statuses = await Promise.all(
|
||||
sortedEvents.map((event) => renderStatus(event, { viewerPubkey: c.get('pubkey') })),
|
||||
sortedEvents.map((event) => renderStatus(event, { viewerPubkey })),
|
||||
);
|
||||
|
||||
// TODO: pagination with min_id and max_id based on the order of `ids`.
|
||||
|
|
Loading…
Reference in New Issue