Add nip19 auth middleware
This commit is contained in:
parent
a0f4a7d9b5
commit
8c48b9f625
|
@ -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 };
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
26
src/app.ts
26
src/app.ts
|
@ -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 };
|
||||||
|
|
19
src/deps.ts
19
src/deps.ts
|
@ -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';
|
||||||
|
|
|
@ -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 };
|
Loading…
Reference in New Issue