Add nip19 auth middleware

This commit is contained in:
Alex Gleason 2023-04-29 15:22:10 -05:00
parent a0f4a7d9b5
commit 8c48b9f625
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
6 changed files with 100 additions and 36 deletions

View File

@ -1,21 +1,17 @@
import { type AppController } from '@/app.ts';
import { fetchUser } from '../client.ts'; import { fetchUser } from '../client.ts';
import { toAccount } from '../transmute.ts'; import { toAccount } from '../transmute.ts';
import { getKeys } from '../utils.ts';
import type { Context } from '@/deps.ts'; const credentialsController: AppController = async (c) => {
const pubkey = c.get('pubkey')!;
async function credentialsController(c: Context) { const event = await fetchUser(pubkey);
const keys = getKeys(c); if (event) {
return c.json(toAccount(event));
if (keys) {
const { pubkey } = keys;
const event = await fetchUser(pubkey);
if (event) {
return c.json(toAccount(event));
}
} }
return c.json({ error: 'Invalid token' }, 400); return c.json({ error: 'Could not find user.' }, 404);
} };
export { credentialsController }; export { credentialsController };

View File

@ -1,22 +1,18 @@
import { type AppController } from '@/app.ts';
import { z } from '@/deps.ts'; import { z } from '@/deps.ts';
import { fetchFeed, fetchFollows } from '../client.ts'; import { fetchFeed, fetchFollows } from '../client.ts';
import { toStatus } from '../transmute.ts'; import { toStatus } from '../transmute.ts';
import { getKeys } from '../utils.ts';
import type { Context } from '@/deps.ts';
import { LOCAL_DOMAIN } from '../config.ts'; import { LOCAL_DOMAIN } from '../config.ts';
async function homeController(c: Context) { const homeController: AppController = async (c) => {
const since = paramSchema.parse(c.req.query('since')); const since = paramSchema.parse(c.req.query('since'));
const until = paramSchema.parse(c.req.query('until')); const until = paramSchema.parse(c.req.query('until'));
const keys = getKeys(c); const pubkey = c.get('pubkey')!;
if (!keys) {
return c.json({ error: 'Unauthorized' }, 401);
}
const follows = await fetchFollows(keys.pubkey); const follows = await fetchFollows(pubkey);
if (!follows) { if (!follows) {
return c.json([]); return c.json([]);
} }
@ -30,7 +26,7 @@ async function homeController(c: Context) {
return c.json(statuses, 200, { return c.json(statuses, 200, {
link: `<${next}>; rel="next", <${prev}>; rel="prev"`, link: `<${next}>; rel="next", <${prev}>; rel="prev"`,
}); });
} };
const paramSchema = z.coerce.number().optional().catch(undefined); const paramSchema = z.coerce.number().optional().catch(undefined);

View File

@ -1,21 +1,21 @@
import { type AppContext } from '@/app.ts';
import { validator, z } from '@/deps.ts'; import { validator, z } from '@/deps.ts';
import { type Event } from '@/nostr/event.ts'; import { type Event } from '@/nostr/event.ts';
import publish from '../publisher.ts'; import publish from '../publisher.ts';
import { toStatus } from '../transmute.ts'; import { toStatus } from '../transmute.ts';
import { getKeys } from '../utils.ts';
const createStatusSchema = z.object({ const createStatusSchema = z.object({
status: z.string(), status: z.string(),
}); });
const createStatusController = validator('json', async (value, c) => { const createStatusController = validator('json', async (value, c: AppContext) => {
const keys = getKeys(c); const pubkey = c.get('pubkey')!;
const seckey = c.get('seckey');
const result = createStatusSchema.safeParse(value); const result = createStatusSchema.safeParse(value);
if (result.success && keys) { if (result.success && seckey) {
const { data } = result; const { data } = result;
const { pubkey, privatekey } = keys;
const event: Event<1> = { const event: Event<1> = {
kind: 1, kind: 1,
@ -25,7 +25,7 @@ const createStatusController = validator('json', async (value, c) => {
created_at: Math.floor(new Date().getTime() / 1000), created_at: Math.floor(new Date().getTime() / 1000),
}; };
publish(event, privatekey); publish(event, seckey);
return c.json(await toStatus(event)); return c.json(await toStatus(event));
} else { } else {

View File

@ -1,4 +1,4 @@
import { cors, Hono } from '@/deps.ts'; import { type Context, cors, type Handler, Hono, type HonoEnv, type MiddlewareHandler } from '@/deps.ts';
import { credentialsController } from './api/accounts.ts'; import { credentialsController } from './api/accounts.ts';
import { appCredentialsController, createAppController } from './api/apps.ts'; import { appCredentialsController, createAppController } from './api/apps.ts';
@ -7,10 +7,22 @@ import homeController from './api/home.ts';
import instanceController from './api/instance.ts'; import instanceController from './api/instance.ts';
import { createTokenController } from './api/oauth.ts'; import { createTokenController } from './api/oauth.ts';
import { createStatusController } from './api/statuses.ts'; import { createStatusController } from './api/statuses.ts';
import { requireAuth, setAuth } from './middleware/auth.ts';
const app = new Hono(); interface AppEnv extends HonoEnv {
Variables: {
pubkey?: string;
seckey?: string;
};
}
app.use('/*', cors()); type AppContext = Context<AppEnv>;
type AppMiddleware = MiddlewareHandler<AppEnv>;
type AppController = Handler<AppEnv>;
const app = new Hono<AppEnv>();
app.use('/*', cors(), setAuth);
app.get('/api/v1/instance', instanceController); app.get('/api/v1/instance', instanceController);
@ -20,11 +32,11 @@ app.post('/api/v1/apps', createAppController);
app.post('/oauth/token', createTokenController); app.post('/oauth/token', createTokenController);
app.post('/oauth/revoke', emptyObjectController); app.post('/oauth/revoke', emptyObjectController);
app.get('/api/v1/accounts/verify_credentials', credentialsController); app.get('/api/v1/accounts/verify_credentials', requireAuth, credentialsController);
app.post('/api/v1/statuses', createStatusController); app.post('/api/v1/statuses', requireAuth, createStatusController);
app.get('/api/v1/timelines/home', homeController); app.get('/api/v1/timelines/home', requireAuth, homeController);
// Not (yet) implemented. // Not (yet) implemented.
app.get('/api/v1/notifications', emptyArrayController); app.get('/api/v1/notifications', emptyArrayController);
@ -39,3 +51,5 @@ app.get('/api/v1/mutes', emptyArrayController);
app.get('/api/v1/domain_blocks', emptyArrayController); app.get('/api/v1/domain_blocks', emptyArrayController);
export default app; export default app;
export type { AppContext, AppController, AppMiddleware };

View File

@ -1,6 +1,21 @@
export { type Context, Hono, validator } from 'https://deno.land/x/hono@v3.0.2/mod.ts'; export {
type Context,
type Env as HonoEnv,
type Handler,
Hono,
type MiddlewareHandler,
validator,
} from 'https://deno.land/x/hono@v3.0.2/mod.ts';
export { HTTPException } from 'https://deno.land/x/hono@v3.0.2/http-exception.ts';
export { cors } from 'https://deno.land/x/hono@v3.0.2/middleware.ts'; export { cors } from 'https://deno.land/x/hono@v3.0.2/middleware.ts';
export { z } from 'https://deno.land/x/zod@v3.20.5/mod.ts'; export { z } from 'https://deno.land/x/zod@v3.20.5/mod.ts';
export { Author, RelayPool } from 'https://dev.jspm.io/nostr-relaypool@0.5.3'; export { Author, RelayPool } from 'https://dev.jspm.io/nostr-relaypool@0.5.3';
export { type Filter, getEventHash, getPublicKey, nip19, signEvent as getSignature } from 'npm:nostr-tools@^1.10.1'; export {
type Filter,
getEventHash,
getPublicKey,
nip19,
nip21,
signEvent as getSignature,
} from 'npm:nostr-tools@^1.10.1';
export { default as lmdb } from 'npm:lmdb'; export { default as lmdb } from 'npm:lmdb';

43
src/middleware/auth.ts Normal file
View File

@ -0,0 +1,43 @@
import { AppMiddleware } from '@/app.ts';
import { getPublicKey, HTTPException, nip19 } from '@/deps.ts';
/** NIP-19 auth middleware. */
const setAuth: AppMiddleware = async (c, next) => {
const authHeader = c.req.headers.get('Authorization');
if (authHeader?.startsWith('Bearer ')) {
const bech32 = authHeader.replace(/^Bearer /, '');
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 requireAuth: AppMiddleware = async (c, next) => {
if (!c.get('pubkey')) {
throw new HTTPException(401);
}
await next();
};
export { requireAuth, setAuth };