mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Delete auth98Middleware, replace with userMiddleware
This commit is contained in:
parent
adeff1cae5
commit
806bfc1b45
7 changed files with 133 additions and 207 deletions
|
|
@ -7,7 +7,6 @@ import { type Context, Handler, Input as HonoInput, MiddlewareHandler } from '@h
|
||||||
import { every } from '@hono/hono/combine';
|
import { every } from '@hono/hono/combine';
|
||||||
import { cors } from '@hono/hono/cors';
|
import { cors } from '@hono/hono/cors';
|
||||||
import { serveStatic } from '@hono/hono/deno';
|
import { serveStatic } from '@hono/hono/deno';
|
||||||
import { createFactory } from '@hono/hono/factory';
|
|
||||||
import { NostrEvent, NostrSigner, NRelay, NUploader } from '@nostrify/nostrify';
|
import { NostrEvent, NostrSigner, NRelay, NUploader } from '@nostrify/nostrify';
|
||||||
|
|
||||||
import '@/startup.ts';
|
import '@/startup.ts';
|
||||||
|
|
@ -138,7 +137,6 @@ import { metricsController } from '@/controllers/metrics.ts';
|
||||||
import { manifestController } from '@/controllers/manifest.ts';
|
import { manifestController } from '@/controllers/manifest.ts';
|
||||||
import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
|
import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts';
|
||||||
import { nostrController } from '@/controllers/well-known/nostr.ts';
|
import { nostrController } from '@/controllers/well-known/nostr.ts';
|
||||||
import { requireProof as _requireProof, requireRole } from '@/middleware/auth98Middleware.ts';
|
|
||||||
import { cacheControlMiddleware } from '@/middleware/cacheControlMiddleware.ts';
|
import { cacheControlMiddleware } from '@/middleware/cacheControlMiddleware.ts';
|
||||||
import { cspMiddleware } from '@/middleware/cspMiddleware.ts';
|
import { cspMiddleware } from '@/middleware/cspMiddleware.ts';
|
||||||
import { metricsMiddleware } from '@/middleware/metricsMiddleware.ts';
|
import { metricsMiddleware } from '@/middleware/metricsMiddleware.ts';
|
||||||
|
|
@ -198,11 +196,6 @@ const ratelimit = every(
|
||||||
rateLimitMiddleware(300, Time.minutes(5), false),
|
rateLimitMiddleware(300, Time.minutes(5), false),
|
||||||
);
|
);
|
||||||
|
|
||||||
const factory = createFactory();
|
|
||||||
const requireSigner = userMiddleware();
|
|
||||||
const requireAdmin = factory.createHandlers(requireSigner, requireRole('admin'));
|
|
||||||
const requireProof = factory.createHandlers(requireSigner, _requireProof());
|
|
||||||
|
|
||||||
app.use('/api/*', metricsMiddleware, ratelimit, paginationMiddleware(), logiMiddleware);
|
app.use('/api/*', metricsMiddleware, ratelimit, paginationMiddleware(), logiMiddleware);
|
||||||
app.use('/.well-known/*', metricsMiddleware, ratelimit, logiMiddleware);
|
app.use('/.well-known/*', metricsMiddleware, ratelimit, logiMiddleware);
|
||||||
app.use('/nodeinfo/*', metricsMiddleware, ratelimit, logiMiddleware);
|
app.use('/nodeinfo/*', metricsMiddleware, ratelimit, logiMiddleware);
|
||||||
|
|
@ -262,27 +255,27 @@ app.post('/oauth/revoke', revokeTokenController);
|
||||||
app.post('/oauth/authorize', oauthAuthorizeController);
|
app.post('/oauth/authorize', oauthAuthorizeController);
|
||||||
app.get('/oauth/authorize', oauthController);
|
app.get('/oauth/authorize', oauthController);
|
||||||
|
|
||||||
app.post('/api/v1/accounts', ...requireProof, createAccountController);
|
app.post('/api/v1/accounts', userMiddleware({ verify: true }), createAccountController);
|
||||||
app.get('/api/v1/accounts/verify_credentials', requireSigner, verifyCredentialsController);
|
app.get('/api/v1/accounts/verify_credentials', userMiddleware(), verifyCredentialsController);
|
||||||
app.patch('/api/v1/accounts/update_credentials', requireSigner, updateCredentialsController);
|
app.patch('/api/v1/accounts/update_credentials', userMiddleware(), updateCredentialsController);
|
||||||
app.get('/api/v1/accounts/search', accountSearchController);
|
app.get('/api/v1/accounts/search', accountSearchController);
|
||||||
app.get('/api/v1/accounts/lookup', accountLookupController);
|
app.get('/api/v1/accounts/lookup', accountLookupController);
|
||||||
app.get('/api/v1/accounts/relationships', requireSigner, relationshipsController);
|
app.get('/api/v1/accounts/relationships', userMiddleware(), relationshipsController);
|
||||||
app.get('/api/v1/accounts/familiar_followers', requireSigner, familiarFollowersController);
|
app.get('/api/v1/accounts/familiar_followers', userMiddleware(), familiarFollowersController);
|
||||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/block', requireSigner, blockController);
|
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/block', userMiddleware(), blockController);
|
||||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unblock', requireSigner, unblockController);
|
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unblock', userMiddleware(), unblockController);
|
||||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/mute', requireSigner, muteController);
|
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/mute', userMiddleware(), muteController);
|
||||||
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unmute', requireSigner, unmuteController);
|
app.post('/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unmute', userMiddleware(), unmuteController);
|
||||||
app.post(
|
app.post(
|
||||||
'/api/v1/accounts/:pubkey{[0-9a-f]{64}}/follow',
|
'/api/v1/accounts/:pubkey{[0-9a-f]{64}}/follow',
|
||||||
rateLimitMiddleware(2, Time.seconds(1)),
|
rateLimitMiddleware(2, Time.seconds(1)),
|
||||||
requireSigner,
|
userMiddleware(),
|
||||||
followController,
|
followController,
|
||||||
);
|
);
|
||||||
app.post(
|
app.post(
|
||||||
'/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unfollow',
|
'/api/v1/accounts/:pubkey{[0-9a-f]{64}}/unfollow',
|
||||||
rateLimitMiddleware(2, Time.seconds(1)),
|
rateLimitMiddleware(2, Time.seconds(1)),
|
||||||
requireSigner,
|
userMiddleware(),
|
||||||
unfollowController,
|
unfollowController,
|
||||||
);
|
);
|
||||||
app.get(
|
app.get(
|
||||||
|
|
@ -306,22 +299,22 @@ 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}}/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}}/context', contextController);
|
||||||
app.get('/api/v1/statuses/:id{[0-9a-f]{64}}', statusController);
|
app.get('/api/v1/statuses/:id{[0-9a-f]{64}}', statusController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/favourite', requireSigner, favouriteController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/favourite', userMiddleware(), favouriteController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/bookmark', requireSigner, bookmarkController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/bookmark', userMiddleware(), bookmarkController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', requireSigner, unbookmarkController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unbookmark', userMiddleware(), unbookmarkController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', requireSigner, pinController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/pin', userMiddleware(), pinController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', requireSigner, unpinController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unpin', userMiddleware(), unpinController);
|
||||||
app.post(
|
app.post(
|
||||||
'/api/v1/statuses/:id{[0-9a-f]{64}}/translate',
|
'/api/v1/statuses/:id{[0-9a-f]{64}}/translate',
|
||||||
requireSigner,
|
userMiddleware(),
|
||||||
rateLimitMiddleware(15, Time.minutes(1)),
|
rateLimitMiddleware(15, Time.minutes(1)),
|
||||||
translatorMiddleware,
|
translatorMiddleware,
|
||||||
translateController,
|
translateController,
|
||||||
);
|
);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/reblog', requireSigner, reblogStatusController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/reblog', userMiddleware(), reblogStatusController);
|
||||||
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unreblog', requireSigner, unreblogStatusController);
|
app.post('/api/v1/statuses/:id{[0-9a-f]{64}}/unreblog', userMiddleware(), unreblogStatusController);
|
||||||
app.post('/api/v1/statuses', requireSigner, createStatusController);
|
app.post('/api/v1/statuses', userMiddleware(), createStatusController);
|
||||||
app.delete('/api/v1/statuses/:id{[0-9a-f]{64}}', requireSigner, deleteStatusController);
|
app.delete('/api/v1/statuses/:id{[0-9a-f]{64}}', userMiddleware(), deleteStatusController);
|
||||||
|
|
||||||
app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/quotes', quotesController);
|
app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/quotes', quotesController);
|
||||||
|
|
||||||
|
|
@ -332,7 +325,7 @@ app.put(
|
||||||
);
|
);
|
||||||
app.post('/api/v2/media', mediaController);
|
app.post('/api/v2/media', mediaController);
|
||||||
|
|
||||||
app.get('/api/v1/timelines/home', rateLimitMiddleware(8, Time.seconds(30)), requireSigner, homeTimelineController);
|
app.get('/api/v1/timelines/home', rateLimitMiddleware(8, Time.seconds(30)), userMiddleware(), homeTimelineController);
|
||||||
app.get('/api/v1/timelines/public', rateLimitMiddleware(8, Time.seconds(30)), publicTimelineController);
|
app.get('/api/v1/timelines/public', rateLimitMiddleware(8, Time.seconds(30)), publicTimelineController);
|
||||||
app.get('/api/v1/timelines/tag/:hashtag', rateLimitMiddleware(8, Time.seconds(30)), hashtagTimelineController);
|
app.get('/api/v1/timelines/tag/:hashtag', rateLimitMiddleware(8, Time.seconds(30)), hashtagTimelineController);
|
||||||
app.get('/api/v1/timelines/suggested', rateLimitMiddleware(8, Time.seconds(30)), suggestedTimelineController);
|
app.get('/api/v1/timelines/suggested', rateLimitMiddleware(8, Time.seconds(30)), suggestedTimelineController);
|
||||||
|
|
@ -368,42 +361,42 @@ app.get('/api/v1/suggestions', suggestionsV1Controller);
|
||||||
app.get('/api/v2/suggestions', suggestionsV2Controller);
|
app.get('/api/v2/suggestions', suggestionsV2Controller);
|
||||||
app.get('/api/v2/ditto/suggestions/local', localSuggestionsController);
|
app.get('/api/v2/ditto/suggestions/local', localSuggestionsController);
|
||||||
|
|
||||||
app.get('/api/v1/notifications', rateLimitMiddleware(8, Time.seconds(30)), requireSigner, notificationsController);
|
app.get('/api/v1/notifications', rateLimitMiddleware(8, Time.seconds(30)), userMiddleware(), notificationsController);
|
||||||
app.get('/api/v1/notifications/:id', requireSigner, notificationController);
|
app.get('/api/v1/notifications/:id', userMiddleware(), notificationController);
|
||||||
|
|
||||||
app.get('/api/v1/favourites', requireSigner, favouritesController);
|
app.get('/api/v1/favourites', userMiddleware(), favouritesController);
|
||||||
app.get('/api/v1/bookmarks', requireSigner, bookmarksController);
|
app.get('/api/v1/bookmarks', userMiddleware(), bookmarksController);
|
||||||
app.get('/api/v1/blocks', requireSigner, blocksController);
|
app.get('/api/v1/blocks', userMiddleware(), blocksController);
|
||||||
app.get('/api/v1/mutes', requireSigner, mutesController);
|
app.get('/api/v1/mutes', userMiddleware(), mutesController);
|
||||||
|
|
||||||
app.get('/api/v1/markers', ...requireProof, markersController);
|
app.get('/api/v1/markers', userMiddleware({ verify: true }), markersController);
|
||||||
app.post('/api/v1/markers', ...requireProof, updateMarkersController);
|
app.post('/api/v1/markers', userMiddleware({ verify: true }), updateMarkersController);
|
||||||
|
|
||||||
app.get('/api/v1/push/subscription', requireSigner, getSubscriptionController);
|
app.get('/api/v1/push/subscription', userMiddleware(), getSubscriptionController);
|
||||||
app.post('/api/v1/push/subscription', ...requireProof, pushSubscribeController);
|
app.post('/api/v1/push/subscription', userMiddleware({ verify: true }), pushSubscribeController);
|
||||||
|
|
||||||
app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions', reactionsController);
|
app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions', reactionsController);
|
||||||
app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', reactionsController);
|
app.get('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', reactionsController);
|
||||||
app.put('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', requireSigner, reactionController);
|
app.put('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', userMiddleware(), reactionController);
|
||||||
app.delete('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', requireSigner, deleteReactionController);
|
app.delete('/api/v1/pleroma/statuses/:id{[0-9a-f]{64}}/reactions/:emoji', userMiddleware(), deleteReactionController);
|
||||||
|
|
||||||
app.get('/api/v1/pleroma/admin/config', ...requireAdmin, configController);
|
app.get('/api/v1/pleroma/admin/config', userMiddleware({ role: 'admin' }), configController);
|
||||||
app.post('/api/v1/pleroma/admin/config', ...requireAdmin, updateConfigController);
|
app.post('/api/v1/pleroma/admin/config', userMiddleware({ role: 'admin' }), updateConfigController);
|
||||||
app.delete('/api/v1/pleroma/admin/statuses/:id', ...requireAdmin, pleromaAdminDeleteStatusController);
|
app.delete('/api/v1/pleroma/admin/statuses/:id', userMiddleware({ role: 'admin' }), pleromaAdminDeleteStatusController);
|
||||||
|
|
||||||
app.get('/api/v1/admin/ditto/relays', ...requireAdmin, adminRelaysController);
|
app.get('/api/v1/admin/ditto/relays', userMiddleware({ role: 'admin' }), adminRelaysController);
|
||||||
app.put('/api/v1/admin/ditto/relays', ...requireAdmin, adminSetRelaysController);
|
app.put('/api/v1/admin/ditto/relays', userMiddleware({ role: 'admin' }), adminSetRelaysController);
|
||||||
|
|
||||||
app.put('/api/v1/admin/ditto/instance', ...requireAdmin, updateInstanceController);
|
app.put('/api/v1/admin/ditto/instance', userMiddleware({ role: 'admin' }), updateInstanceController);
|
||||||
|
|
||||||
app.post('/api/v1/ditto/names', requireSigner, nameRequestController);
|
app.post('/api/v1/ditto/names', userMiddleware(), nameRequestController);
|
||||||
app.get('/api/v1/ditto/names', requireSigner, nameRequestsController);
|
app.get('/api/v1/ditto/names', userMiddleware(), nameRequestsController);
|
||||||
|
|
||||||
app.get('/api/v1/ditto/captcha', rateLimitMiddleware(3, Time.minutes(1)), captchaController);
|
app.get('/api/v1/ditto/captcha', rateLimitMiddleware(3, Time.minutes(1)), captchaController);
|
||||||
app.post(
|
app.post(
|
||||||
'/api/v1/ditto/captcha/:id/verify',
|
'/api/v1/ditto/captcha/:id/verify',
|
||||||
rateLimitMiddleware(8, Time.minutes(1)),
|
rateLimitMiddleware(8, Time.minutes(1)),
|
||||||
...requireProof,
|
userMiddleware({ verify: true }),
|
||||||
captchaVerifyController,
|
captchaVerifyController,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -414,44 +407,59 @@ app.get(
|
||||||
);
|
);
|
||||||
app.get('/api/v1/ditto/:id{[0-9a-f]{64}}/zap_splits', statusZapSplitsController);
|
app.get('/api/v1/ditto/:id{[0-9a-f]{64}}/zap_splits', statusZapSplitsController);
|
||||||
|
|
||||||
app.put('/api/v1/admin/ditto/zap_splits', ...requireAdmin, updateZapSplitsController);
|
app.put('/api/v1/admin/ditto/zap_splits', userMiddleware({ role: 'admin' }), updateZapSplitsController);
|
||||||
app.delete('/api/v1/admin/ditto/zap_splits', ...requireAdmin, deleteZapSplitsController);
|
app.delete('/api/v1/admin/ditto/zap_splits', userMiddleware({ role: 'admin' }), deleteZapSplitsController);
|
||||||
|
|
||||||
app.post('/api/v1/ditto/zap', requireSigner, zapController);
|
app.post('/api/v1/ditto/zap', userMiddleware(), zapController);
|
||||||
app.get('/api/v1/ditto/statuses/:id{[0-9a-f]{64}}/zapped_by', zappedByController);
|
app.get('/api/v1/ditto/statuses/:id{[0-9a-f]{64}}/zapped_by', zappedByController);
|
||||||
|
|
||||||
app.route('/api/v1/ditto/cashu', cashuApp);
|
app.route('/api/v1/ditto/cashu', cashuApp);
|
||||||
|
|
||||||
app.post('/api/v1/reports', requireSigner, reportController);
|
app.post('/api/v1/reports', userMiddleware(), reportController);
|
||||||
app.get('/api/v1/admin/reports', requireSigner, ...requireAdmin, adminReportsController);
|
app.get('/api/v1/admin/reports', userMiddleware(), userMiddleware({ role: 'admin' }), adminReportsController);
|
||||||
app.get('/api/v1/admin/reports/:id{[0-9a-f]{64}}', requireSigner, ...requireAdmin, adminReportController);
|
app.get(
|
||||||
|
'/api/v1/admin/reports/:id{[0-9a-f]{64}}',
|
||||||
|
userMiddleware(),
|
||||||
|
userMiddleware({ role: 'admin' }),
|
||||||
|
adminReportController,
|
||||||
|
);
|
||||||
app.post(
|
app.post(
|
||||||
'/api/v1/admin/reports/:id{[0-9a-f]{64}}/resolve',
|
'/api/v1/admin/reports/:id{[0-9a-f]{64}}/resolve',
|
||||||
requireSigner,
|
userMiddleware(),
|
||||||
...requireAdmin,
|
userMiddleware({ role: 'admin' }),
|
||||||
adminReportResolveController,
|
adminReportResolveController,
|
||||||
);
|
);
|
||||||
app.post(
|
app.post(
|
||||||
'/api/v1/admin/reports/:id{[0-9a-f]{64}}/reopen',
|
'/api/v1/admin/reports/:id{[0-9a-f]{64}}/reopen',
|
||||||
requireSigner,
|
userMiddleware(),
|
||||||
...requireAdmin,
|
userMiddleware({ role: 'admin' }),
|
||||||
adminReportReopenController,
|
adminReportReopenController,
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get('/api/v1/admin/accounts', ...requireAdmin, adminAccountsController);
|
app.get('/api/v1/admin/accounts', userMiddleware({ role: 'admin' }), adminAccountsController);
|
||||||
app.post('/api/v1/admin/accounts/:id{[0-9a-f]{64}}/action', requireSigner, ...requireAdmin, adminActionController);
|
app.post(
|
||||||
|
'/api/v1/admin/accounts/:id{[0-9a-f]{64}}/action',
|
||||||
|
userMiddleware(),
|
||||||
|
userMiddleware({ role: 'admin' }),
|
||||||
|
adminActionController,
|
||||||
|
);
|
||||||
app.post(
|
app.post(
|
||||||
'/api/v1/admin/accounts/:id{[0-9a-f]{64}}/approve',
|
'/api/v1/admin/accounts/:id{[0-9a-f]{64}}/approve',
|
||||||
requireSigner,
|
userMiddleware(),
|
||||||
...requireAdmin,
|
userMiddleware({ role: 'admin' }),
|
||||||
adminApproveController,
|
adminApproveController,
|
||||||
);
|
);
|
||||||
app.post('/api/v1/admin/accounts/:id{[0-9a-f]{64}}/reject', requireSigner, ...requireAdmin, adminRejectController);
|
app.post(
|
||||||
|
'/api/v1/admin/accounts/:id{[0-9a-f]{64}}/reject',
|
||||||
|
userMiddleware(),
|
||||||
|
userMiddleware({ role: 'admin' }),
|
||||||
|
adminRejectController,
|
||||||
|
);
|
||||||
|
|
||||||
app.put('/api/v1/pleroma/admin/users/tag', ...requireAdmin, pleromaAdminTagController);
|
app.put('/api/v1/pleroma/admin/users/tag', userMiddleware({ role: 'admin' }), pleromaAdminTagController);
|
||||||
app.delete('/api/v1/pleroma/admin/users/tag', ...requireAdmin, pleromaAdminUntagController);
|
app.delete('/api/v1/pleroma/admin/users/tag', userMiddleware({ role: 'admin' }), pleromaAdminUntagController);
|
||||||
app.patch('/api/v1/pleroma/admin/users/suggest', ...requireAdmin, pleromaAdminSuggestController);
|
app.patch('/api/v1/pleroma/admin/users/suggest', userMiddleware({ role: 'admin' }), pleromaAdminSuggestController);
|
||||||
app.patch('/api/v1/pleroma/admin/users/unsuggest', ...requireAdmin, pleromaAdminUnsuggestController);
|
app.patch('/api/v1/pleroma/admin/users/unsuggest', userMiddleware({ role: 'admin' }), pleromaAdminUnsuggestController);
|
||||||
|
|
||||||
// Not (yet) implemented.
|
// Not (yet) implemented.
|
||||||
app.get('/api/v1/custom_emojis', emptyArrayController);
|
app.get('/api/v1/custom_emojis', emptyArrayController);
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ const createCashuWalletAndNutzapInfoSchema = z.object({
|
||||||
* https://github.com/nostr-protocol/nips/blob/master/60.md
|
* https://github.com/nostr-protocol/nips/blob/master/60.md
|
||||||
* https://github.com/nostr-protocol/nips/blob/master/61.md#nutzap-informational-event
|
* https://github.com/nostr-protocol/nips/blob/master/61.md#nutzap-informational-event
|
||||||
*/
|
*/
|
||||||
route.put('/wallet', userMiddleware('nip44'), async (c) => {
|
route.put('/wallet', userMiddleware({ enc: 'nip44' }), async (c) => {
|
||||||
const { conf, user, relay, signal } = c.var;
|
const { conf, user, relay, signal } = c.var;
|
||||||
|
|
||||||
const pubkey = await user.signer.getPublicKey();
|
const pubkey = await user.signer.getPublicKey();
|
||||||
|
|
@ -104,7 +104,7 @@ route.put('/wallet', userMiddleware('nip44'), async (c) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Gets a wallet, if it exists. */
|
/** Gets a wallet, if it exists. */
|
||||||
route.get('/wallet', userMiddleware('nip44'), swapNutzapsMiddleware, async (c) => {
|
route.get('/wallet', userMiddleware({ enc: 'nip44' }), swapNutzapsMiddleware, async (c) => {
|
||||||
const { conf, relay, user, signal } = c.var;
|
const { conf, relay, user, signal } = c.var;
|
||||||
|
|
||||||
const pubkey = await user.signer.getPublicKey();
|
const pubkey = await user.signer.getPublicKey();
|
||||||
|
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
import { buildAuthEventTemplate, parseAuthRequest, type ParseAuthRequestOpts, validateAuthEvent } from '@ditto/nip98';
|
|
||||||
import { HTTPException } from '@hono/hono/http-exception';
|
|
||||||
import { NostrEvent } from '@nostrify/nostrify';
|
|
||||||
|
|
||||||
import { type AppContext, type AppMiddleware } from '@/app.ts';
|
|
||||||
import { ReadOnlySigner } from '@/signers/ReadOnlySigner.ts';
|
|
||||||
import { localRequest } from '@/utils/api.ts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NIP-98 auth.
|
|
||||||
* https://github.com/nostr-protocol/nips/blob/master/98.md
|
|
||||||
*/
|
|
||||||
function auth98Middleware(opts: ParseAuthRequestOpts = {}): AppMiddleware {
|
|
||||||
return async (c, next) => {
|
|
||||||
const req = localRequest(c);
|
|
||||||
const result = await parseAuthRequest(req, opts);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
const user = {
|
|
||||||
relay: c.var.relay,
|
|
||||||
signer: new ReadOnlySigner(result.data.pubkey),
|
|
||||||
...c.var.user,
|
|
||||||
};
|
|
||||||
|
|
||||||
c.set('user', user);
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
const { conf, relay } = c.var;
|
|
||||||
|
|
||||||
const [user] = await relay.query([{
|
|
||||||
kinds: [30382],
|
|
||||||
authors: [await conf.signer.getPublicKey()],
|
|
||||||
'#d': [proof.pubkey],
|
|
||||||
limit: 1,
|
|
||||||
}]);
|
|
||||||
|
|
||||||
if (user && matchesRole(user, role)) {
|
|
||||||
await next();
|
|
||||||
} else {
|
|
||||||
throw new HTTPException(401);
|
|
||||||
}
|
|
||||||
}, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Require the user to demonstrate they own the pubkey by signing an event. */
|
|
||||||
function requireProof(opts?: ParseAuthRequestOpts): AppMiddleware {
|
|
||||||
return withProof(async (_c, _proof, next) => {
|
|
||||||
await next();
|
|
||||||
}, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Check whether the user fulfills the role. */
|
|
||||||
function matchesRole(user: NostrEvent, role: UserRole): boolean {
|
|
||||||
return user.tags.some(([tag, value]) => tag === 'n' && value === role);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** HOC to obtain proof in middleware. */
|
|
||||||
function withProof(
|
|
||||||
handler: (c: AppContext, proof: NostrEvent, next: () => Promise<void>) => Promise<void>,
|
|
||||||
opts?: ParseAuthRequestOpts,
|
|
||||||
): AppMiddleware {
|
|
||||||
return async (c, next) => {
|
|
||||||
const signer = c.var.user?.signer;
|
|
||||||
const pubkey = await 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.
|
|
||||||
if (proof && pubkey && pubkey !== proof.pubkey) {
|
|
||||||
throw new HTTPException(401, { message: 'Pubkey mismatch' });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proof) {
|
|
||||||
c.set('proof', proof);
|
|
||||||
|
|
||||||
if (!signer) {
|
|
||||||
const user = {
|
|
||||||
relay: c.var.relay,
|
|
||||||
signer: new ReadOnlySigner(proof.pubkey),
|
|
||||||
...c.var.user,
|
|
||||||
};
|
|
||||||
|
|
||||||
c.set('user', user);
|
|
||||||
}
|
|
||||||
|
|
||||||
await handler(c, proof, next);
|
|
||||||
} else {
|
|
||||||
throw new HTTPException(401, { message: 'No proof' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the proof over Nostr Connect. */
|
|
||||||
async function obtainProof(c: AppContext, opts?: ParseAuthRequestOpts) {
|
|
||||||
const signer = c.var.user?.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 signer.signEvent(reqEvent);
|
|
||||||
const result = await validateAuthEvent(req, resEvent, opts);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
return result.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { auth98Middleware, requireProof, requireRole };
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { type Context } from '@hono/hono';
|
|
||||||
import { HTTPException } from '@hono/hono/http-exception';
|
import { HTTPException } from '@hono/hono/http-exception';
|
||||||
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
import { NostrEvent, NostrFilter } from '@nostrify/nostrify';
|
||||||
import { logi } from '@soapbox/logi';
|
import { logi } from '@soapbox/logi';
|
||||||
|
|
@ -257,13 +256,6 @@ function paginatedList(
|
||||||
return c.json(results, 200, headers);
|
return c.json(results, 200, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Rewrite the URL of the request object to use the local domain. */
|
|
||||||
function localRequest(c: Context): Request {
|
|
||||||
return Object.create(c.req.raw, {
|
|
||||||
url: { value: Conf.local(c.req.url) },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Actors with Bluesky's `!no-unauthenticated` self-label should require authorization to view. */
|
/** Actors with Bluesky's `!no-unauthenticated` self-label should require authorization to view. */
|
||||||
function assertAuthenticated(c: AppContext, author: NostrEvent): void {
|
function assertAuthenticated(c: AppContext, author: NostrEvent): void {
|
||||||
if (
|
if (
|
||||||
|
|
@ -282,7 +274,6 @@ export {
|
||||||
createAdminEvent,
|
createAdminEvent,
|
||||||
createEvent,
|
createEvent,
|
||||||
type EventStub,
|
type EventStub,
|
||||||
localRequest,
|
|
||||||
paginated,
|
paginated,
|
||||||
paginatedList,
|
paginatedList,
|
||||||
parseBody,
|
parseBody,
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,4 @@ import type { NostrSigner, NRelay } from '@nostrify/nostrify';
|
||||||
export interface User<S extends NostrSigner = NostrSigner, R extends NRelay = NRelay> {
|
export interface User<S extends NostrSigner = NostrSigner, R extends NRelay = NRelay> {
|
||||||
signer: S;
|
signer: S;
|
||||||
relay: R;
|
relay: R;
|
||||||
verified?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ export function tokenMiddleware(): DittoMiddleware<{ user?: User }> {
|
||||||
const user: User = {
|
const user: User = {
|
||||||
signer,
|
signer,
|
||||||
relay: new UserStore({ relay, userPubkey, adminPubkey }),
|
relay: new UserStore({ relay, userPubkey, adminPubkey }),
|
||||||
verified: auth.realm === 'Nostr',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
c.set('user', user);
|
c.set('user', user);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,29 @@
|
||||||
|
import { buildAuthEventTemplate, validateAuthEvent } from '@ditto/nip98';
|
||||||
import { HTTPException } from '@hono/hono/http-exception';
|
import { HTTPException } from '@hono/hono/http-exception';
|
||||||
|
|
||||||
import type { DittoMiddleware } from '@ditto/router';
|
import type { DittoMiddleware } from '@ditto/router';
|
||||||
import type { NostrSigner } from '@nostrify/nostrify';
|
import type { NostrEvent, NostrSigner } from '@nostrify/nostrify';
|
||||||
import type { SetRequired } from 'type-fest';
|
import type { SetRequired } from 'type-fest';
|
||||||
import type { User } from './User.ts';
|
import type { User } from './User.ts';
|
||||||
|
|
||||||
type Nip44Signer = SetRequired<NostrSigner, 'nip44'>;
|
type Nip44Signer = SetRequired<NostrSigner, 'nip44'>;
|
||||||
|
|
||||||
|
interface UserMiddlewareOpts {
|
||||||
|
enc?: 'nip04' | 'nip44';
|
||||||
|
role?: string;
|
||||||
|
verify?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function userMiddleware(): DittoMiddleware<{ user: User }>;
|
export function userMiddleware(): DittoMiddleware<{ user: User }>;
|
||||||
// @ts-ignore Types are right.
|
// @ts-ignore Types are right.
|
||||||
export function userMiddleware(enc: 'nip44'): DittoMiddleware<{ user: User<Nip44Signer> }>;
|
export function userMiddleware(
|
||||||
export function userMiddleware(enc?: 'nip04' | 'nip44'): DittoMiddleware<{ user: User }> {
|
opts: UserMiddlewareOpts & { enc: 'nip44' },
|
||||||
|
): DittoMiddleware<{ user: User<Nip44Signer> }>;
|
||||||
|
export function userMiddleware(opts: UserMiddlewareOpts): DittoMiddleware<{ user: User }>;
|
||||||
|
export function userMiddleware(opts: UserMiddlewareOpts = {}): DittoMiddleware<{ user: User }> {
|
||||||
return async (c, next) => {
|
return async (c, next) => {
|
||||||
const { user } = c.var;
|
const { conf, user, relay } = c.var;
|
||||||
|
const { enc, role, verify } = opts;
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new HTTPException(403, { message: 'Authorization required.' });
|
throw new HTTPException(403, { message: 'Authorization required.' });
|
||||||
|
|
@ -22,6 +33,45 @@ export function userMiddleware(enc?: 'nip04' | 'nip44'): DittoMiddleware<{ user:
|
||||||
throw new HTTPException(403, { message: `User does not have a ${enc} signer` });
|
throw new HTTPException(403, { message: `User does not have a ${enc} signer` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role || verify) {
|
||||||
|
const req = setRequestUrl(c.req.raw, conf.local(c.req.url));
|
||||||
|
const reqEvent = await buildAuthEventTemplate(req);
|
||||||
|
const resEvent = await user.signer.signEvent(reqEvent);
|
||||||
|
const result = await validateAuthEvent(req, resEvent);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new HTTPException(403, { message: 'Verification failed.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent people from accidentally using the wrong account. This has no other security implications.
|
||||||
|
if (result.data.pubkey !== await user.signer.getPublicKey()) {
|
||||||
|
throw new HTTPException(401, { message: 'Pubkey mismatch' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role) {
|
||||||
|
const [user] = await relay.query([{
|
||||||
|
kinds: [30382],
|
||||||
|
authors: [await conf.signer.getPublicKey()],
|
||||||
|
'#d': [result.data.pubkey],
|
||||||
|
limit: 1,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
if (!user || !matchesRole(user, role)) {
|
||||||
|
throw new HTTPException(403, { message: `Must have ${role} role.` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Rewrite the URL of the request object. */
|
||||||
|
function setRequestUrl(req: Request, url: string): Request {
|
||||||
|
return Object.create(req, { url: { value: url } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check whether the user fulfills the role. */
|
||||||
|
function matchesRole(user: NostrEvent, role: string): boolean {
|
||||||
|
return user.tags.some(([tag, value]) => tag === 'n' && value === role);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue