diff --git a/OAUTH-LIKE-FLOW.md b/OAUTH-LIKE-FLOW.md new file mode 100644 index 0000000..9856d8a --- /dev/null +++ b/OAUTH-LIKE-FLOW.md @@ -0,0 +1,36 @@ +# OAuth-like flow + +The OAuth-like flow is a way to create new users in the bunker. + +The goal of this flow is to provide a flow that is familiar for new users that are not familiar with key management and that doesn't requrie installing extensions. + +The way it works is, a new user without a nostr account goes to an client that implements this flow, when they click register the following happen: + +* the client should ask for the user desired NIP-05. + * to accomplish this, the client can hardcode using their own backend or they can use NIP-89 to find nsecbunker providers. + * if using non-trusted (i.e. from NIP-89) the client should validate that the bunker's pubkey `kind:0` has a valid NIP-05 with the `_@domain` identifier. +* the client generates a local key and stores it in the user's device. **This is the local key the client will use to sign on behalf of the user** +* the client uses this key to send an `create_account` command to the selected bunker: +```json +{ + "content": nip04_encrypt("{ + method: "create_account", + params: [ + { + email: "", + username: "", + domain: "" // it should be available in this bunker + } + ] + }") +} +``` +* the bunker might reply with an `auth_url` response. The client opens this URL. The client might include a `redirect_uri` parameter where the user should be redirected to. +* after signup/client-authorization the client's `create_account` request will be responded with the new user's pubkey or, if a `redirect_uri` was provided, the user will be redirected to the `redirect_uri` with the new user's pubkey as a query string parameter (`pubkey`). + * in this screen the client can issue a `connect` NIP-46 request to the user's pubkey to verify that everything is working. + +## NIP-05 +In the background, the bunker will have configured the requested NIP-05 mapping so that the user can use this nostr address to login next time. + +## NIP-47 +To complete the experience, allowing new users to have a LN wallet immmediately available is very interesting. The bunker can optionally create an LNBits-backed wallet with zapping capabilities. \ No newline at end of file diff --git a/package.json b/package.json index 63e4af2..39c7540 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nsecbunkerd", - "version": "0.8.2", + "version": "0.9.0", "description": "nsecbunker daemon", "main": "dist/index.js", "bin": { @@ -20,6 +20,7 @@ }, "scripts": { "build": "tsup src/index.ts; tsup src/daemon/index.ts -d dist/daemon; tsup src/client.ts -d dist/client", + "build:client": "tsup src/client.ts -d dist/client", "prisma:generate": "npx prisma generate", "prisma:migrate": "npx prisma migrate deploy", "prisma:create": "npx prisma db push --preview-feature", @@ -38,7 +39,7 @@ "@fastify/view": "^8.2.0", "@inquirer/password": "^1.1.2", "@inquirer/prompts": "^1.2.3", - "@nostr-dev-kit/ndk": "^2.3.0", + "@nostr-dev-kit/ndk": "^2.3.1", "@prisma/client": "^5.4.1", "@scure/base": "^1.1.1", "@types/yargs": "^17.0.24", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aee4dd3..257a7ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ dependencies: specifier: ^1.2.3 version: 1.2.3 '@nostr-dev-kit/ndk': - specifier: ^2.2.0 - version: 2.2.0(typescript@5.1.3) + specifier: ^2.3.1 + version: 2.3.1(typescript@5.1.3) '@prisma/client': specifier: ^5.4.1 version: 5.4.1(prisma@5.4.1) @@ -40,10 +40,10 @@ dependencies: version: 16.3.1 eslint-config-prettier: specifier: ^8.8.0 - version: 8.8.0(eslint@8.55.0) + version: 8.8.0(eslint@8.56.0) eslint-plugin-import: specifier: ^2.27.5 - version: 2.27.5(eslint@8.55.0) + version: 2.27.5(eslint@8.56.0) eventemitter3: specifier: ^5.0.1 version: 5.0.1 @@ -307,13 +307,13 @@ packages: dev: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.55.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.55.0 + eslint: 8.56.0 eslint-visitor-keys: 3.4.3 dev: false @@ -329,7 +329,7 @@ packages: ajv: 6.12.6 debug: 4.3.4 espree: 9.6.1 - globals: 13.23.0 + globals: 13.24.0 ignore: 5.3.0 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -339,8 +339,8 @@ packages: - supports-color dev: false - /@eslint/js@8.55.0: - resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==} + /@eslint/js@8.56.0: + resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false @@ -582,6 +582,11 @@ packages: engines: {node: '>= 16'} dev: false + /@noble/hashes@1.3.3: + resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} + engines: {node: '>= 16'} + dev: false + /@noble/secp256k1@2.0.0: resolution: {integrity: sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==} dev: false @@ -604,10 +609,10 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 - /@nostr-dev-kit/ndk@2.2.0(typescript@5.1.3): - resolution: {integrity: sha512-NdnErX8em9Y/qC4CVYTHYE0bvtLV2ZQh56+JOiarjeJd+J7ZdJX1P10ba463iEOodppvKZqFlYbuDU6CprehUA==} + /@nostr-dev-kit/ndk@2.3.1(typescript@5.1.3): + resolution: {integrity: sha512-T9raZyRXJstYWWVIyQIz6dAQVpth+TIda/+OHfuYnxuY6q6Lz5T0jPvue9cMSHrzWmmtUA6yZiJ1PfgzvRn8+g==} dependencies: - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@noble/secp256k1': 2.0.0 '@scure/base': 1.1.1 debug: 4.3.4 @@ -1365,13 +1370,13 @@ packages: engines: {node: '>=10'} dev: false - /eslint-config-prettier@8.8.0(eslint@8.55.0): + /eslint-config-prettier@8.8.0(eslint@8.56.0): resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.55.0 + eslint: 8.56.0 dev: false /eslint-import-resolver-node@0.3.7: @@ -1384,7 +1389,7 @@ packages: - supports-color dev: false - /eslint-module-utils@2.8.0(eslint-import-resolver-node@0.3.7)(eslint@8.55.0): + /eslint-module-utils@2.8.0(eslint-import-resolver-node@0.3.7)(eslint@8.56.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -1406,13 +1411,13 @@ packages: optional: true dependencies: debug: 3.2.7 - eslint: 8.55.0 + eslint: 8.56.0 eslint-import-resolver-node: 0.3.7 transitivePeerDependencies: - supports-color dev: false - /eslint-plugin-import@2.27.5(eslint@8.55.0): + /eslint-plugin-import@2.27.5(eslint@8.56.0): resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} peerDependencies: @@ -1427,9 +1432,9 @@ packages: array.prototype.flatmap: 1.3.1 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.55.0 + eslint: 8.56.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.8.0(eslint-import-resolver-node@0.3.7)(eslint@8.55.0) + eslint-module-utils: 2.8.0(eslint-import-resolver-node@0.3.7)(eslint@8.56.0) has: 1.0.3 is-core-module: 2.12.1 is-glob: 4.0.3 @@ -1457,15 +1462,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false - /eslint@8.55.0: - resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==} + /eslint@8.56.0: + resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@eslint-community/regexpp': 4.10.0 '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.55.0 + '@eslint/js': 8.56.0 '@humanwhocodes/config-array': 0.11.13 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -1485,7 +1490,7 @@ packages: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.23.0 + globals: 13.24.0 graphemer: 1.4.0 ignore: 5.3.0 imurmurhash: 0.1.4 @@ -1912,8 +1917,8 @@ packages: path-is-absolute: 1.0.1 dev: false - /globals@13.23.0: - resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 diff --git a/src/client.ts b/src/client.ts index 76f908b..5741cc0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3,17 +3,21 @@ import NDK, { NDKUser, NDKEvent, NDKPrivateKeySigner, NDKNip46Signer, NostrEvent import fs from 'fs'; const command = process.argv[2]; -const remotePubkey = process.argv[3]; -const content = process.argv[4]; +let remotePubkey = process.argv[3]; +let content = process.argv[4]; const dontPublish = process.argv.includes('--dont-publish'); const debug = process.argv.includes('--debug'); +let signer: NDKNip46Signer; +let ndk: NDK; +let remoteUser: NDKUser; if (!command) { console.log('Usage: node src/client.js [--dont-publish] [--debug] [--pk ]'); console.log(''); console.log(`\t: command to run (ping, sign)`); console.log(`\t: npub that should be published as`); - console.log(`\t: event JSON to sign (no need for pubkey or id fields) | or kind:1 content string to sign`); + console.log(`\t: sign flow: event JSON to sign (no need for pubkey or id fields) | or kind:1 content string to sign\n`); + console.log(`\t create_account flow: [desired-nip05[,desired-domain,[email]]]`); console.log('\t--debug: enable debug mode'); process.exit(1); } @@ -24,8 +28,8 @@ async function createNDK(): Promise { enableOutboxModel: false }); if (debug) { - ndk.pool.on('connect', () => console.log('✅ connected')); - ndk.pool.on('disconnect', () => console.log('❌ disconnected')); + ndk.pool.on('relay:connect', () => console.log('✅ connected')); + ndk.pool.on('relay:disconnect', () => console.log('❌ disconnected')); } await ndk.connect(5000); @@ -59,11 +63,35 @@ function loadPrivateKey(): string | undefined { (async () => { - const remoteUser = new NDKUser({npub: remotePubkey}); - const ndk = await createNDK(); + let remoteUser: NDKUser; + + // if this is the create_account command and we have something that doesn't look like an npub as the remotePubkey, use NDKUser.fromNip05 to get the npub + if (command === 'create_account' && !remotePubkey.startsWith("npub")) { + // see if we have a username@domain + let [ username, domain ] = remotePubkey.split('@'); + + if (!domain) { + domain = username; + username = Math.random().toString(36).substring(2, 15); + } + + content = `${username},${domain}` + + const u = await NDKUser.fromNip05(domain); + if (!u) { + console.log(`Invalid nip05 ${remotePubkey}`); + process.exit(1); + } + remoteUser = u; + remotePubkey = remoteUser.pubkey; + } else { + remoteUser = new NDKUser({npub: remotePubkey}); + } + + ndk = await createNDK(); + let localSigner: NDKPrivateKeySigner; const pk = loadPrivateKey(); - let localSigner: NDKPrivateKeySigner; if (pk) { localSigner = new NDKPrivateKeySigner(pk); @@ -72,7 +100,7 @@ function loadPrivateKey(): string | undefined { savePrivateKey(localSigner.privateKey!); } - const signer = new NDKNip46Signer(ndk, remoteUser.hexpubkey, localSigner); + signer = new NDKNip46Signer(ndk, remoteUser.pubkey, localSigner); if (debug) console.log(`local pubkey`, (await localSigner.user()).npub); if (debug) console.log(`remote pubkey`, remotePubkey); ndk.signer = signer; @@ -81,6 +109,27 @@ function loadPrivateKey(): string | undefined { console.log(`Go to ${url} to authorize this request`); }); + switch (command) { + case "sign": return signFlow(); + case "create_account": return createAccountFlow(); + default: + console.log(`Unknown command ${command}`); + process.exit(1); + } +})(); + +async function createAccountFlow() { + const [ username, domain, email ] = content.split(',').map((s) => s.trim()); + try { + const pubkey = await signer.createAccount(username, domain, email); + const user = new NDKUser({pubkey}); + console.log(`Hello`, user.npub); + } catch (e) { + console.log('error', e); + } +} + +function signFlow() { setTimeout(async () => { try { if (debug) console.log(`waiting for authorization (check your nsecBunker)...`); @@ -109,8 +158,6 @@ function loadPrivateKey(): string | undefined { } as NostrEvent); } - event.pubkey = remoteUser.hexpubkey; - try { await event.sign(); if (debug) { @@ -124,4 +171,4 @@ function loadPrivateKey(): string | undefined { console.log('sign error', e); } }, 2000); -})(); \ No newline at end of file +} \ No newline at end of file diff --git a/src/daemon/admin/commands/create_account.ts b/src/daemon/admin/commands/create_account.ts index fb555a9..568bfa0 100644 --- a/src/daemon/admin/commands/create_account.ts +++ b/src/daemon/admin/commands/create_account.ts @@ -1,4 +1,4 @@ -import { Hexpubkey, NDKPrivateKeySigner, NDKRpcRequest, NDKUserProfile } from "@nostr-dev-kit/ndk"; +import { Hexpubkey, NDKKind, NDKPrivateKeySigner, NDKRpcRequest, NDKUserProfile } from "@nostr-dev-kit/ndk"; import AdminInterface from ".."; import { nip19 } from 'nostr-tools'; import { setupSkeletonProfile } from "../../lib/profile"; @@ -8,7 +8,7 @@ import { allowAllRequestsFromKey } from "../../lib/acl"; import { requestAuthorization } from "../../authorize"; import prisma from "../../../db"; -export async function validate(currentConfig, email: string, username: string, domain: string) { +export async function validate(currentConfig, username: string, domain: string, email?: string) { if (!username) { throw new Error('username is required'); } @@ -47,32 +47,82 @@ async function addNip05(currentConfig: IConfig, username: string, domain: string writeFileSync(nip05File, JSON.stringify(currentNip05s, null, 2)); } +async function validateUsername(username: string | undefined, domain: string, admin: AdminInterface, req: NDKRpcRequest) { + if (!username || username.length === 0) { + // create a random username of 10 characters + username = Math.random().toString(36).substring(2, 15); + } + + return username; +} + +async function validateDomain(domain: string | undefined, admin: AdminInterface, req: NDKRpcRequest) { + const availableDomains = (await admin.config()).domains; + + if (!availableDomains || Object.keys(availableDomains).length === 0) + throw new Error('no domains available'); + + if (!domain || domain.length === 0) domain = Object.keys(availableDomains)[0]; + + // check if the domain is available + if (!availableDomains[domain]) { + throw new Error('domain not available'); + } + + return domain; +} + export default async function createAccount(admin: AdminInterface, req: NDKRpcRequest) { - const [ payload ] = req.params as [ string ]; - const { email, username, domain } = JSON.parse(payload); + let [ username, domain, email ] = req.params as [ string?, string?, string? ]; + + try { + domain = await validateDomain(domain, admin, req); + username = await validateUsername(username, domain, admin, req); + } catch (e: any) { + admin.rpc.sendResponse(req.id, req.pubkey, "error", NDKKind.NostrConnectAdmin, e.message); + return; + } + const nip05 = `${username}@${domain}`; - if ( - await requestAuthorization( - admin, - nip05, - req.pubkey, - req.id, - req.method, - payload - ) - ) { - console.log('authorized'); - return createAccountReal(admin, req); + const payload: string[] = [ username, domain ]; + if (email) payload.push(email); + + console.log('requesting authorization', payload); + + const authorizationWithPayload = await requestAuthorization( + admin, + nip05, + req.pubkey, + req.id, + req.method, + JSON.stringify(payload) + ); + console.log('authorizationWithPayload', authorizationWithPayload); + + if (authorizationWithPayload) { + const payload = JSON.parse(authorizationWithPayload); + username = payload[0]; + domain = payload[1]; + email = payload[2]; + return createAccountReal(admin, req, username, domain, email); } } -export async function createAccountReal(admin: AdminInterface, req: NDKRpcRequest) { +export async function createAccountReal( + admin: AdminInterface, + req: NDKRpcRequest, + username: string, + domain: string, + email?: string +) { + // Fetch record since the authorization backend might have changed it + + + console.log('creating account'); try { const currentConfig = await getCurrentConfig(admin.configFile); - const [ payload ] = req.params as [ string ]; - const { email, username, domain } = JSON.parse(payload); - await validate(currentConfig, email, username, domain); + await validate(currentConfig, username, domain, email); const nip05 = `${username}@${domain}`; const key = NDKPrivateKeySigner.generate(); @@ -91,8 +141,6 @@ export async function createAccountReal(admin: AdminInterface, req: NDKRpcReques const nsec = nip19.nsecEncode(key.privateKey!); currentConfig.keys[keyName] = { key: key.privateKey }; - console.log('saving new key', {keyName, privateKey: key.privateKey}); - saveCurrentConfig(admin.configFile, currentConfig); await admin.loadNsec!(keyName, nsec); @@ -102,16 +150,11 @@ export async function createAccountReal(admin: AdminInterface, req: NDKRpcReques // Immediately grant access to the creator key await grantPermissions(req, keyName); - return admin.rpc.sendResponse(req.id, req.pubkey, JSON.stringify([ - generatedUser.pubkey, - - ])); + return admin.rpc.sendResponse(req.id, req.pubkey, generatedUser.pubkey, NDKKind.NostrConnectAdmin); } catch (e: any) { console.trace('error', e); - return admin.rpc.sendResponse(req.id, req.pubkey, JSON.stringify([ - "error", - e.message - ]), 24134); + return admin.rpc.sendResponse(req.id, req.pubkey, "error", NDKKind.NostrConnectAdmin, + e.message); } } diff --git a/src/daemon/admin/index.ts b/src/daemon/admin/index.ts index bd5bf4e..c671905 100644 --- a/src/daemon/admin/index.ts +++ b/src/daemon/admin/index.ts @@ -1,5 +1,5 @@ import "websocket-polyfill"; -import NDK, { NDKEvent, NDKPrivateKeySigner, NDKRpcRequest, NDKRpcResponse, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk'; +import NDK, { NDKEvent, NDKKind, NDKPrivateKeySigner, NDKRpcRequest, NDKRpcResponse, NDKUser, NostrEvent } from '@nostr-dev-kit/ndk'; import { NDKNostrRpc } from '@nostr-dev-kit/ndk'; import { debug } from 'debug'; import { Key, KeyUser } from '../run'; @@ -106,8 +106,8 @@ class AdminInterface { this.ndk.connect(2500).then(() => { // connect for whitelisted admins this.rpc.subscribe({ - "kinds": [24134 as number], - "#p": [this.signerUser!.pubkey], + "kinds": [NDKKind.NostrConnect, 24134 as number], + "#p": [this.signerUser!.pubkey] }); this.rpc.on('request', (req) => this.handleRequest(req)); @@ -146,12 +146,13 @@ class AdminInterface { } } catch (err: any) { console.error(`Error handling request ${req.method}: ${err.message}`, req.params); - return this.rpc.sendResponse(req.id, req.pubkey, JSON.stringify(['error', err?.message]), 24134); + return this.rpc.sendResponse(req.id, req.pubkey, "error", NDKKind.NostrConnectAdmin, err?.message); } } private async validateRequest(req: NDKRpcRequest): Promise { // if this request is of type create_account, allow it + // TODO: require some POW to prevent spam if (req.method === 'create_account' && allowNewKeys) { console.log(`allowing create_account request`); return; diff --git a/src/daemon/authorize.ts b/src/daemon/authorize.ts index 927ab4d..58ff39d 100644 --- a/src/daemon/authorize.ts +++ b/src/daemon/authorize.ts @@ -28,18 +28,23 @@ export async function requestAuthorization( console.log('baseUrl', baseUrl); } - return new Promise((resolve) => { + return new Promise((resolve, reject) => { if (baseUrl) { // If we have a URL, request authorization through web - urlAuthFlow(baseUrl, admin, remotePubkey, requestId, request, resolve); + urlAuthFlow(baseUrl, admin, remotePubkey, requestId, request, resolve, reject); } - adminAuthFlow(admin, keyName, remotePubkey, method, param, resolve); + adminAuthFlow(admin, keyName, remotePubkey, method, param, resolve, reject); }); } -async function adminAuthFlow(adminInterface, keyName, remotePubkey, method, param, resolve) { +async function adminAuthFlow(adminInterface, keyName, remotePubkey, method, param, resolve, reject) { const requestedPerm = await adminInterface.requestPermission(keyName, remotePubkey, method, param); - return requestedPerm; + + if (requestedPerm) { + resolve(); + } else { + reject(); + } } async function createRecord( @@ -82,12 +87,11 @@ export function urlAuthFlow( remotePubkey: Hexpubkey, requestId: string, request: Request, - resolve: any + resolve: any, + reject: any ) { const url = generatePendingAuthUrl(baseUrl, request); - console.log({url}); - admin.rpc.sendResponse(requestId, remotePubkey, "auth_url", undefined, url); // Regularly poll to see if this request was approved so we can synchronously resolve @@ -106,7 +110,11 @@ export function urlAuthFlow( if (record.allowed !== undefined && record.allowed !== null) { clearInterval(checkingInterval); - resolve(!!record.allowed); + + if (record.allowed === false) { + reject(record.payload); + } + resolve(record.params); } }, 100); } diff --git a/src/daemon/web/authorize.ts b/src/daemon/web/authorize.ts index c862d1d..79cc9e7 100644 --- a/src/daemon/web/authorize.ts +++ b/src/daemon/web/authorize.ts @@ -19,10 +19,7 @@ export async function authorizeRequestWebHandler(request, reply) { if (method === "create_account") { const payload = JSON.parse(record.params); - console.log({payload}); - email = payload.email; - username = payload.username; - domain = payload.domain; + const [ username, domain, email ] = payload; nip05 = `${username}@${domain}`; return reply.view("/templates/createAccount.handlebar", { record, email, username, domain, nip05, callbackUrl }); @@ -74,14 +71,27 @@ export async function processRegistrationWebHandler(request, reply) { const record = await prisma.request.findUnique({ where: { id: request.params.id } }); + const body = request.body; if (!record || record.allowed) { return { ok: false, error: "Request not found or already processed" }; } + // we serialize the payload again and store it + // along with the allowed flag + // so that the original caller can get the current state + // to be processed + const payload: string[] = []; + payload.push(body.username); + payload.push(body.domain); + payload.push(body.email); + payload.push(body.password); + + // TODO: validations here + await prisma.request.update({ where: { id: request.params.id }, - data: { allowed: true } + data: { params: JSON.stringify(payload), allowed: true } }); let createdPubkey: string | undefined; @@ -100,7 +110,6 @@ export async function processRegistrationWebHandler(request, reply) { }, 100); }); - const body = request.body; const callbackUrlString = body.callbackUrl; let callbackUrl: string | undefined; diff --git a/templates/createAccount.handlebar b/templates/createAccount.handlebar index 653966c..4278ae3 100644 --- a/templates/createAccount.handlebar +++ b/templates/createAccount.handlebar @@ -51,14 +51,15 @@ @{{domain}} - + +