mirror of
https://github.com/aljazceru/ditto.git
synced 2026-01-25 16:24:20 +01:00
Support nip98 auth
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { AppMiddleware } from '@/app.ts';
|
||||
import { type AppMiddleware } from '@/app.ts';
|
||||
import { getPublicKey, HTTPException, nip19 } from '@/deps.ts';
|
||||
|
||||
/** The token includes a Bech32 Nostr ID (npub, nsec, etc) and an optional session ID. */
|
||||
@@ -7,7 +7,7 @@ const TOKEN_REGEX = new RegExp(`(${nip19.BECH32_REGEX.source})(?:_(\\w+))?`);
|
||||
const BEARER_REGEX = new RegExp(`^Bearer (${TOKEN_REGEX.source})$`);
|
||||
|
||||
/** NIP-19 auth middleware. */
|
||||
const setAuth: AppMiddleware = async (c, next) => {
|
||||
const auth19: AppMiddleware = async (c, next) => {
|
||||
const authHeader = c.req.headers.get('authorization');
|
||||
const match = authHeader?.match(BEARER_REGEX);
|
||||
|
||||
@@ -47,4 +47,4 @@ const requireAuth: AppMiddleware = async (c, next) => {
|
||||
await next();
|
||||
};
|
||||
|
||||
export { requireAuth, setAuth, TOKEN_REGEX };
|
||||
export { auth19, requireAuth, TOKEN_REGEX };
|
||||
74
src/middleware/auth98.ts
Normal file
74
src/middleware/auth98.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { type AppMiddleware } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { HTTPException } from '@/deps.ts';
|
||||
import { type Event } from '@/event.ts';
|
||||
import { decode64Schema, jsonSchema, signedEventSchema } from '@/schema.ts';
|
||||
import { eventAge, findTag, sha256, Time } from '@/utils.ts';
|
||||
|
||||
const decodeEventSchema = decode64Schema.pipe(jsonSchema).pipe(signedEventSchema);
|
||||
|
||||
interface Auth98Opts {
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* NIP-98 auth.
|
||||
* https://github.com/nostr-protocol/nips/blob/master/98.md
|
||||
*/
|
||||
function auth98(opts: Auth98Opts = {}): AppMiddleware {
|
||||
return async (c, next) => {
|
||||
const authHeader = c.req.headers.get('authorization');
|
||||
const base64 = authHeader?.match(/^Nostr (.+)$/)?.[1];
|
||||
const { timeout = Time.minutes(1) } = opts;
|
||||
|
||||
const schema = decodeEventSchema
|
||||
.refine((event) => event.kind === 27235)
|
||||
.refine((event) => eventAge(event) < timeout)
|
||||
.refine((event) => findTag(event.tags, 'method')?.[1] === c.req.method)
|
||||
.refine((event) => {
|
||||
const url = findTag(event.tags, 'u')?.[1];
|
||||
try {
|
||||
return url === localUrl(c.req.url);
|
||||
} catch (_e) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.refine(async (event) => {
|
||||
const body = await c.req.raw.clone().text();
|
||||
if (!body) return true;
|
||||
const hash = findTag(event.tags, 'payload')?.[1];
|
||||
return hash === await sha256(body);
|
||||
});
|
||||
|
||||
const result = await schema.safeParseAsync(base64);
|
||||
|
||||
if (result.success) {
|
||||
c.set('pubkey', result.data.pubkey);
|
||||
c.set('proof', result.data as Event<27235>);
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
}
|
||||
|
||||
function localUrl(url: string): string {
|
||||
const { pathname } = new URL(url);
|
||||
return new URL(pathname, Conf.localDomain).toString();
|
||||
}
|
||||
|
||||
const requireProof: AppMiddleware = async (c, next) => {
|
||||
const pubkey = c.get('pubkey');
|
||||
const proof = c.get('proof');
|
||||
|
||||
// if (!proof && hasWebsocket(c.req)) {
|
||||
// // TODO: attempt to sign nip98 event through websocket
|
||||
// }
|
||||
|
||||
if (!pubkey || !proof || proof.pubkey !== pubkey) {
|
||||
throw new HTTPException(401);
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
|
||||
export { auth98, requireProof };
|
||||
Reference in New Issue
Block a user