From 825fed7d21152476e3b9e211a1b27f82333386c5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 4 Mar 2023 22:10:56 -0600 Subject: [PATCH] This is probably enough to log in? --- src/api/accounts.ts | 44 ++++++++++++++++++++++++++++++++++++++++++-- src/client.ts | 37 +++++++++++++++++++++++++++++++++++++ src/config.ts | 2 ++ src/deps.ts | 2 ++ src/event.ts | 27 +++++++++++++++++++++++++++ src/schema.ts | 17 +++++++++++++++++ 6 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/client.ts create mode 100644 src/event.ts create mode 100644 src/schema.ts diff --git a/src/api/accounts.ts b/src/api/accounts.ts index 104165ab..120f7fd3 100644 --- a/src/api/accounts.ts +++ b/src/api/accounts.ts @@ -1,7 +1,47 @@ +import { getPublicKey } from '@/deps.ts'; + +import { LOCAL_DOMAIN } from '../config.ts'; +import { fetchUser } from '../client.ts'; +import { MetaContent, metaContentSchema } from '../schema.ts'; + import type { Context } from '@/deps.ts'; -function credentialsController(c: Context) { - return c.json({}); +async function credentialsController(c: Context) { + const authHeader = c.req.headers.get('Authorization') || ''; + + if (authHeader.startsWith('Bearer ')) { + const token = authHeader.split('Bearer ')[1]; + const pubkey = getPublicKey(token); + const event = await fetchUser(pubkey); + const parsed = metaContentSchema.safeParse(JSON.parse(event?.content || '')); + const content: MetaContent = parsed.success ? parsed.data : {}; + const { host, origin } = new URL(LOCAL_DOMAIN); + + return c.json({ + id: pubkey, + acct: pubkey, + avatar: content.picture, + avatar_static: content.picture, + bot: false, + created_at: event ? new Date(event.created_at * 1000).toISOString() : new Date().toISOString(), + display_name: content.name, + emojis: [], + fields: [], + follow_requests_count: 0, + followers_count: 0, + following_count: 0, + statuses_count: 0, + header: content.banner, + header_static: content.banner, + locked: false, + note: content.about, + fqn: `${pubkey}@${host}`, + url: `${origin}/users/${pubkey}`, + username: pubkey, + }); + } + + return c.json({ error: 'Invalid token' }, 400); } export { credentialsController }; diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 00000000..a9ae376c --- /dev/null +++ b/src/client.ts @@ -0,0 +1,37 @@ +import { Author, RelayPool } from '@/deps.ts'; + +import { poolRelays } from './config.ts'; + +import type { Event } from './event.ts'; + +const pool = new RelayPool(poolRelays); + +/** Fetch a Nostr event by its ID. */ +const fetchEvent = async (id: string): Promise => { + const event = await (pool.getEventById(id, poolRelays, 0) as Promise); + return event?.id === id ? event : null; +}; + +/** Fetch a Nostr `set_medatadata` event for a user's pubkey. */ +const fetchUser = async (pubkey: string): Promise | null> => { + const author = new Author(pool, poolRelays, pubkey); + const event: Event<0> | null = await new Promise((resolve) => author.metaData(resolve, 0)); + return event?.pubkey === pubkey ? event : null; +}; + +/** Fetch users the given pubkey follows. */ +const fetchFollows = (pubkey: string): Promise | null> => { + return new Promise((resolve) => { + pool.subscribe( + [{ authors: [pubkey], kinds: [3] }], + poolRelays, + (event: Event<3> | null) => { + resolve(event?.pubkey === pubkey ? event : null); + }, + undefined, + undefined, + ); + }); +}; + +export { fetchEvent, fetchFollows, fetchUser }; diff --git a/src/config.ts b/src/config.ts index cedd9045..66c9fab0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,2 +1,4 @@ export const LOCAL_DOMAIN = Deno.env.get('LOCAL_DOMAIN') || 'http://localhost:8000'; export const POST_CHAR_LIMIT = Number(Deno.env.get('POST_CHAR_LIMIT') || 5000); + +export const poolRelays = (Deno.env.get('RELAY_POOL') || '').split(',').filter(Boolean); diff --git a/src/deps.ts b/src/deps.ts index f8f4a098..12042cab 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -1,4 +1,6 @@ import { Context, Hono, validator } from 'https://deno.land/x/hono@v3.0.2/mod.ts'; export { Hono, validator }; 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 { getPublicKey } from 'https://dev.jspm.io/nostr-tools@1.6.0'; export type { Context }; diff --git a/src/event.ts b/src/event.ts new file mode 100644 index 00000000..14e47a7d --- /dev/null +++ b/src/event.ts @@ -0,0 +1,27 @@ +enum Kind { + Metadata = 0, + Text = 1, + RecommendRelay = 2, + Contacts = 3, + EncryptedDirectMessage = 4, + EventDeletion = 5, + DeprecatedRepost = 6, + Reaction = 7, + ChannelCreation = 40, + ChannelMetadata = 41, + ChannelMessage = 42, + ChannelHideMessage = 43, + ChannelMuteUser = 44, +} + +interface Event { + id?: string; + sig?: string; + kind: K; + tags: string[][]; + pubkey: string; + content: string; + created_at: number; +} + +export type { Event, Kind }; diff --git a/src/schema.ts b/src/schema.ts new file mode 100644 index 00000000..3d35f250 --- /dev/null +++ b/src/schema.ts @@ -0,0 +1,17 @@ +import { z } from '@/deps.ts'; + +const optionalString = z.string().optional().catch(undefined); + +const metaContentSchema = z.object({ + name: optionalString, + about: optionalString, + picture: optionalString, + banner: optionalString, + nip05: optionalString, + lud16: optionalString, +}); + +type MetaContent = z.infer; + +export { metaContentSchema }; +export type { MetaContent };