wire localhost screens

This commit is contained in:
boufni95 2024-07-19 22:30:09 +02:00
parent c2cab40a2e
commit a8d447110f
36 changed files with 6075 additions and 4966 deletions

2
.gitignore vendored
View file

@ -14,3 +14,5 @@ logs
data/
.wallet_secret
.wallet_password
.admin_enroll
admin.npub

View file

@ -13,11 +13,51 @@ The nostr server will send back a message response, and inside the body there wi
## NOSTR Methods
### These are the nostr methods the client implements to communicate with the API via nostr
- LndGetInfo
- auth type: __Admin__
- input: [LndGetInfoRequest](#LndGetInfoRequest)
- output: [LndGetInfoResponse](#LndGetInfoResponse)
- AddApp
- auth type: __Admin__
- input: [AddAppRequest](#AddAppRequest)
- output: [AuthApp](#AuthApp)
- AuthApp
- auth type: __Admin__
- input: [AuthAppRequest](#AuthAppRequest)
- output: [AuthApp](#AuthApp)
- BanUser
- auth type: __Admin__
- input: [BanUserRequest](#BanUserRequest)
- output: [BanUserResponse](#BanUserResponse)
- GetUsageMetrics
- auth type: __Metrics__
- This methods has an __empty__ __request__ body
- output: [UsageMetrics](#UsageMetrics)
- GetAppsMetrics
- auth type: __Metrics__
- input: [AppsMetricsRequest](#AppsMetricsRequest)
- output: [AppsMetrics](#AppsMetrics)
- GetLndMetrics
- auth type: __Metrics__
- input: [LndMetricsRequest](#LndMetricsRequest)
- output: [LndMetrics](#LndMetrics)
- LinkNPubThroughToken
- auth type: __User__
- input: [LinkNPubThroughTokenRequest](#LinkNPubThroughTokenRequest)
- This methods has an __empty__ __response__ body
- EnrollAdminToken
- auth type: __User__
- input: [EnrollAdminTokenRequest](#EnrollAdminTokenRequest)
- This methods has an __empty__ __response__ body
- UserHealth
- auth type: __User__
- This methods has an __empty__ __request__ body
@ -120,9 +160,9 @@ The nostr server will send back a message response, and inside the body there wi
- __User__:
- expected context content
- __user_id__: _string_
- __app_id__: _string_
- __app_user_id__: _string_
- __user_id__: _string_
- __Admin__:
- expected context content
@ -265,6 +305,13 @@ The nostr server will send back a message response, and inside the body there wi
- input: [LinkNPubThroughTokenRequest](#LinkNPubThroughTokenRequest)
- This methods has an __empty__ __response__ body
- EnrollAdminToken
- auth type: __User__
- http method: __post__
- http route: __/api/guest/npub/enroll/admin__
- input: [EnrollAdminTokenRequest](#EnrollAdminTokenRequest)
- This methods has an __empty__ __response__ body
- GetApp
- auth type: __App__
- http method: __post__
@ -482,47 +529,36 @@ The nostr server will send back a message response, and inside the body there wi
## Messages
### The content of requests and response from the methods
### LnurlLinkResponse
- __lnurl__: _string_
- __k1__: _string_
### Empty
### RelaysMigration
- __relays__: ARRAY of: _string_
### RequestNPubLinkingTokenResponse
- __token__: _string_
### BannedAppUser
- __app_name__: _string_
- __app_id__: _string_
- __user_identifier__: _string_
- __nostr_pub__: _string_
### Application
- __name__: _string_
- __id__: _string_
- __balance__: _number_
- __npub__: _string_
### PayInvoiceResponse
- __preimage__: _string_
- __amount_paid__: _number_
- __operation_id__: _string_
- __service_fee__: _number_
- __network_fee__: _number_
### OpenChannelRequest
- __destination__: _string_
- __fundingAmount__: _number_
- __pushAmount__: _number_
- __closeAddress__: _string_
### LndMetrics
- __nodes__: ARRAY of: _[LndNodeMetrics](#LndNodeMetrics)_
### LndMetricsRequest
- __from_unix__: _number_ *this field is optional
- __to_unix__: _number_ *this field is optional
### LndGetInfoResponse
- __alias__: _string_
### PayAppUserInvoiceRequest
- __user_identifier__: _string_
- __invoice__: _string_
- __amount__: _number_
### Product
- __id__: _string_
- __name__: _string_
- __price_sats__: _number_
### LndGetInfoRequest
- __nodeId__: _number_
### BanUserResponse
- __balance_sats__: _number_
- __banned_app_users__: ARRAY of: _[BannedAppUser](#BannedAppUser)_
### AuthAppRequest
- __name__: _string_
- __allow_user_creation__: _boolean_ *this field is optional
### PayAddressRequest
- __address__: _string_
- __amoutSats__: _number_
@ -533,69 +569,94 @@ The nostr server will send back a message response, and inside the body there wi
- __to_unix__: _number_ *this field is optional
- __include_operations__: _boolean_ *this field is optional
### AppsMetrics
- __apps__: ARRAY of: _[AppMetrics](#AppMetrics)_
### RoutingEvent
- __timestamp_ns__: _number_
- __event_type__: _string_
- __settled__: _boolean_
- __offchain__: _boolean_
- __incoming_channel_id__: _number_
- __incoming_htlc_id__: _number_
- __outgoing_channel_id__: _number_
- __outgoing_htlc_id__: _number_
- __timestamp_ns__: _number_
- __event_type__: _string_
- __forward_fail_event__: _boolean_
- __incoming_amt_msat__: _number_
- __outgoing_amt_msat__: _number_
- __failure_string__: _string_
- __settled__: _boolean_
- __offchain__: _boolean_
- __forward_fail_event__: _boolean_
### ClosedChannel
- __channel_id__: _string_
- __capacity__: _number_
- __closed_height__: _number_
### BannedAppUser
- __app_name__: _string_
- __app_id__: _string_
- __user_identifier__: _string_
- __nostr_pub__: _string_
### AddAppRequest
### GetUserOperationsResponse
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
### RequestNPubLinkingTokenResponse
- __token__: _string_
### UsageMetric
- __processed_at_ms__: _number_
- __auth_in_nano__: _number_
- __rpc_name__: _string_
- __nostr__: _boolean_
- __batch_size__: _number_
- __parsed_in_nano__: _number_
- __validate_in_nano__: _number_
- __handle_in_nano__: _number_
- __batch__: _boolean_
### NewInvoiceRequest
- __amountSats__: _number_
- __memo__: _string_
### OpenChannelRequest
- __destination__: _string_
- __fundingAmount__: _number_
- __pushAmount__: _number_
- __closeAddress__: _string_
### ChainBalanceEvent
- __block_height__: _number_
- __confirmed_balance__: _number_
- __unconfirmed_balance__: _number_
- __total_balance__: _number_
### GetAppUserRequest
- __user_identifier__: _string_
### NewAddressResponse
- __address__: _string_
### PayInvoiceResponse
- __preimage__: _string_
- __amount_paid__: _number_
- __operation_id__: _string_
- __service_fee__: _number_
- __network_fee__: _number_
### AddProductRequest
- __name__: _string_
- __allow_user_creation__: _boolean_
- __price_sats__: _number_
### MigrationUpdate
- __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional
- __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional
### ClosureMigration
- __closes_at_unix__: _number_
### BanUserResponse
- __balance_sats__: _number_
- __banned_app_users__: ARRAY of: _[BannedAppUser](#BannedAppUser)_
### AddAppUserRequest
- __identifier__: _string_
- __fail_if_exists__: _boolean_
- __balance__: _number_
### SetMockAppUserBalanceRequest
- __user_identifier__: _string_
- __amount__: _number_
### PayInvoiceRequest
- __invoice__: _string_
- __amount__: _number_
### LnurlWithdrawInfoResponse
- __tag__: _string_
- __callback__: _string_
- __k1__: _string_
- __defaultDescription__: _string_
- __minWithdrawable__: _number_
- __maxWithdrawable__: _number_
- __balanceCheck__: _string_
- __payLink__: _string_
### UsageMetric
- __processed_at_ms__: _number_
- __parsed_in_nano__: _number_
- __auth_in_nano__: _number_
- __validate_in_nano__: _number_
- __handle_in_nano__: _number_
- __rpc_name__: _string_
- __batch__: _boolean_
- __nostr__: _boolean_
- __batch_size__: _number_
### EncryptionExchangeRequest
- __publicKey__: _string_
- __deviceId__: _string_
### UsersInfo
- __total__: _number_
@ -605,207 +666,70 @@ The nostr server will send back a message response, and inside the body there wi
- __balance_avg__: _number_
- __balance_median__: _number_
### BanUserRequest
- __user_id__: _string_
### UserOperations
- __fromIndex__: _number_
- __toIndex__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### RequestNPubLinkingTokenRequest
- __user_identifier__: _string_
### UserOperation
- __paidAtUnix__: _number_
- __type__: _[UserOperationType](#UserOperationType)_
- __inbound__: _boolean_
- __amount__: _number_
- __identifier__: _string_
- __operationId__: _string_
- __service_fee__: _number_
- __network_fee__: _number_
- __confirmed__: _boolean_
- __tx_hash__: _string_
- __internal__: _boolean_
### Product
- __id__: _string_
- __name__: _string_
- __price_sats__: _number_
### LinkNPubThroughTokenRequest
- __token__: _string_
- __nostr_pub__: _string_
### EncryptionExchangeRequest
- __publicKey__: _string_
- __deviceId__: _string_
### PayAppUserInvoiceRequest
- __user_identifier__: _string_
- __invoice__: _string_
- __amount__: _number_
### SendAppUserToAppPaymentRequest
- __from_user_identifier__: _string_
- __amount__: _number_
### SendAppUserToAppUserPaymentRequest
- __from_user_identifier__: _string_
- __to_user_identifier__: _string_
- __amount__: _number_
### NewInvoiceRequest
- __amountSats__: _number_
- __memo__: _string_
### MigrationUpdate
- __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional
- __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional
### GetUserOperationsResponse
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
### AddAppUserInvoiceRequest
- __receiver_identifier__: _string_
- __payer_identifier__: _string_
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### DecodeInvoiceRequest
- __invoice__: _string_
### UserInfo
- __userId__: _string_
- __balance__: _number_
- __max_withdrawable__: _number_
- __user_identifier__: _string_
- __service_fee_bps__: _number_
- __network_max_fee_bps__: _number_
- __network_max_fee_fixed__: _number_
### DecodeInvoiceResponse
- __amount__: _number_
### OpenChannelResponse
- __channelId__: _string_
### LiveUserOperation
- __operation__: _[UserOperation](#UserOperation)_
### UsageMetrics
- __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_
### AuthApp
- __app__: _[Application](#Application)_
- __auth_token__: _string_
### AddAppInvoiceRequest
- __payer_identifier__: _string_
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### HandleLnurlPayResponse
- __pr__: _string_
- __routes__: ARRAY of: _[Empty](#Empty)_
### Empty
### ChannelBalanceEvent
- __block_height__: _number_
- __channel_id__: _string_
- __local_balance_sats__: _number_
- __remote_balance_sats__: _number_
### GetAppUserRequest
- __user_identifier__: _string_
### ChannelRouting
- __receive_errors__: _number_
- __forward_errors_as_output__: _number_
- __missed_forward_fee_as_output__: _number_
- __events_number__: _number_
- __channel_id__: _string_
- __forward_errors_as_input__: _number_
- __missed_forward_fee_as_input__: _number_
- __forward_fee_as_input__: _number_
- __forward_fee_as_output__: _number_
- __send_errors__: _number_
### AddProductRequest
- __name__: _string_
- __price_sats__: _number_
### AppUser
- __identifier__: _string_
- __info__: _[UserInfo](#UserInfo)_
- __max_withdrawable__: _number_
### GetAppUserLNURLInfoRequest
- __user_identifier__: _string_
- __base_url_override__: _string_
### SendAppUserToAppPaymentRequest
- __from_user_identifier__: _string_
- __amount__: _number_
### SetMockAppBalanceRequest
- __amount__: _number_
### NewAddressRequest
- __addressType__: _[AddressType](#AddressType)_
### GetProductBuyLinkResponse
- __link__: _string_
### HttpCreds
- __url__: _string_
- __token__: _string_
### AppMetrics
- __app__: _[Application](#Application)_
- __users__: _[UsersInfo](#UsersInfo)_
- __received__: _number_
- __spent__: _number_
- __available__: _number_
- __fees__: _number_
- __invoices__: _number_
- __total_fees__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### ClosedChannel
- __channel_id__: _string_
- __capacity__: _number_
- __closed_height__: _number_
### ChainBalanceEvent
- __block_height__: _number_
- __confirmed_balance__: _number_
- __unconfirmed_balance__: _number_
- __total_balance__: _number_
### Application
- __id__: _string_
- __balance__: _number_
- __npub__: _string_
- __name__: _string_
### SetMockInvoiceAsPaidRequest
- __invoice__: _string_
### AddAppUserRequest
- __balance__: _number_
- __identifier__: _string_
- __fail_if_exists__: _boolean_
### SetMockAppUserBalanceRequest
- __user_identifier__: _string_
- __amount__: _number_
### LndGetInfoRequest
- __nodeId__: _number_
### NewAddressResponse
- __address__: _string_
### PayAddressResponse
- __txId__: _string_
- __operation_id__: _string_
- __service_fee__: _number_
- __network_fee__: _number_
- __txId__: _string_
### AppsMetrics
- __apps__: ARRAY of: _[AppMetrics](#AppMetrics)_
### NewInvoiceResponse
- __invoice__: _string_
### LndNodeMetrics
- __channels_balance_events__: ARRAY of: _[ChannelBalanceEvent](#ChannelBalanceEvent)_
- __chain_balance_events__: ARRAY of: _[ChainBalanceEvent](#ChainBalanceEvent)_
- __offline_channels__: _number_
- __online_channels__: _number_
- __pending_channels__: _number_
- __closing_channels__: _number_
- __open_channels__: ARRAY of: _[OpenChannel](#OpenChannel)_
- __closed_channels__: ARRAY of: _[ClosedChannel](#ClosedChannel)_
- __channel_routing__: ARRAY of: _[ChannelRouting](#ChannelRouting)_
### RequestNPubLinkingTokenRequest
- __user_identifier__: _string_
### AuthAppRequest
- __name__: _string_
- __allow_user_creation__: _boolean_ *this field is optional
### LndMetricsRequest
- __from_unix__: _number_ *this field is optional
- __to_unix__: _number_ *this field is optional
### LinkNPubThroughTokenRequest
- __token__: _string_
- __nostr_pub__: _string_
### OpenChannel
- __channel_id__: _string_
@ -815,38 +739,164 @@ The nostr server will send back a message response, and inside the body there wi
- __local_balance__: _number_
- __remote_balance__: _number_
### NewInvoiceResponse
### SetMockInvoiceAsPaidRequest
- __invoice__: _string_
- __amount__: _number_
### AddAppUserInvoiceRequest
- __receiver_identifier__: _string_
- __payer_identifier__: _string_
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### DecodeInvoiceResponse
- __amount__: _number_
### OpenChannelResponse
- __channelId__: _string_
### RelaysMigration
- __relays__: ARRAY of: _string_
### LndMetrics
- __nodes__: ARRAY of: _[LndNodeMetrics](#LndNodeMetrics)_
### AppUser
- __identifier__: _string_
- __info__: _[UserInfo](#UserInfo)_
- __max_withdrawable__: _number_
### AddAppInvoiceRequest
- __payer_identifier__: _string_
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### UserOperations
- __fromIndex__: _number_
- __toIndex__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### BanUserRequest
- __user_id__: _string_
### NewAddressRequest
- __addressType__: _[AddressType](#AddressType)_
### DecodeInvoiceRequest
- __invoice__: _string_
### ChannelRouting
- __channel_id__: _string_
- __send_errors__: _number_
- __receive_errors__: _number_
- __forward_errors_as_input__: _number_
- __forward_errors_as_output__: _number_
- __missed_forward_fee_as_input__: _number_
- __missed_forward_fee_as_output__: _number_
- __forward_fee_as_input__: _number_
- __forward_fee_as_output__: _number_
- __events_number__: _number_
### LnurlPayInfoResponse
- __tag__: _string_
### LnurlWithdrawInfoResponse
- __callback__: _string_
- __maxSendable__: _number_
- __minSendable__: _number_
- __metadata__: _string_
- __allowsNostr__: _boolean_
- __nostrPubkey__: _string_
- __k1__: _string_
- __defaultDescription__: _string_
- __minWithdrawable__: _number_
- __maxWithdrawable__: _number_
- __balanceCheck__: _string_
- __payLink__: _string_
- __tag__: _string_
### GetUserOperationsRequest
- __latestIncomingInvoice__: _number_
- __latestOutgoingInvoice__: _number_
- __latestIncomingTx__: _number_
- __latestOutgoingTx__: _number_
- __latestIncomingUserToUserPayment__: _number_
- __latestOutgoingUserToUserPayment__: _number_
- __max_size__: _number_
- __latestIncomingInvoice__: _number_
### AppMetrics
- __spent__: _number_
- __available__: _number_
- __fees__: _number_
- __invoices__: _number_
- __total_fees__: _number_
- __app__: _[Application](#Application)_
- __users__: _[UsersInfo](#UsersInfo)_
- __received__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### AddAppRequest
- __name__: _string_
- __allow_user_creation__: _boolean_
### SendAppUserToAppUserPaymentRequest
- __amount__: _number_
- __from_user_identifier__: _string_
- __to_user_identifier__: _string_
### LnurlPayInfoResponse
- __callback__: _string_
- __maxSendable__: _number_
- __minSendable__: _number_
- __metadata__: _string_
- __allowsNostr__: _boolean_
- __nostrPubkey__: _string_
- __tag__: _string_
### HandleLnurlPayResponse
- __pr__: _string_
- __routes__: ARRAY of: _[Empty](#Empty)_
### EnrollAdminTokenRequest
- __admin_token__: _string_
### UsageMetrics
- __metrics__: ARRAY of: _[UsageMetric](#UsageMetric)_
### LnurlLinkResponse
- __k1__: _string_
- __lnurl__: _string_
### LiveUserOperation
- __operation__: _[UserOperation](#UserOperation)_
### PayInvoiceRequest
- __invoice__: _string_
- __amount__: _number_
### GetProductBuyLinkResponse
- __link__: _string_
### LndNodeMetrics
- __chain_balance_events__: ARRAY of: _[ChainBalanceEvent](#ChainBalanceEvent)_
- __online_channels__: _number_
- __closing_channels__: _number_
- __channels_balance_events__: ARRAY of: _[ChannelBalanceEvent](#ChannelBalanceEvent)_
- __pending_channels__: _number_
- __open_channels__: ARRAY of: _[OpenChannel](#OpenChannel)_
- __closed_channels__: ARRAY of: _[ClosedChannel](#ClosedChannel)_
- __channel_routing__: ARRAY of: _[ChannelRouting](#ChannelRouting)_
- __offline_channels__: _number_
### AuthApp
- __app__: _[Application](#Application)_
- __auth_token__: _string_
### GetAppUserLNURLInfoRequest
- __user_identifier__: _string_
- __base_url_override__: _string_
### UserInfo
- __network_max_fee_fixed__: _number_
- __userId__: _string_
- __balance__: _number_
- __max_withdrawable__: _number_
- __user_identifier__: _string_
- __service_fee_bps__: _number_
- __network_max_fee_bps__: _number_
### UserOperation
- __paidAtUnix__: _number_
- __service_fee__: _number_
- __confirmed__: _boolean_
- __tx_hash__: _string_
- __internal__: _boolean_
- __network_fee__: _number_
- __type__: _[UserOperationType](#UserOperationType)_
- __inbound__: _boolean_
- __amount__: _number_
- __identifier__: _string_
- __operationId__: _string_
## Enums
### The enumerators used in the messages

File diff suppressed because it is too large Load diff

View file

@ -364,6 +364,28 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.EnrollAdminToken) throw new Error('method: EnrollAdminToken is not implemented')
app.post('/api/guest/npub/enroll/admin', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'EnrollAdminToken', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.EnrollAdminToken) throw new Error('method: EnrollAdminToken is not implemented')
const authContext = await opts.UserAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.EnrollAdminTokenRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
await methods.EnrollAdminToken({rpcName:'EnrollAdminToken', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetApp) throw new Error('method: GetApp is not implemented')
app.post('/api/app/get', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetApp', batch: false, nostr: false, batchSize: 0}
@ -948,6 +970,18 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'EnrollAdminToken':
if (!methods.EnrollAdminToken) {
throw new Error('method EnrollAdminToken not found' )
} else {
const error = Types.EnrollAdminTokenRequestValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.EnrollAdminToken({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UserHealth':
if (!methods.UserHealth) {
throw new Error('method UserHealth not found' )
@ -1121,6 +1155,6 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
var server: { close: () => void } | undefined
return {
Close: () => { if (!server) { throw new Error('tried closing server before starting') } else server.close() },
Listen: (port: number) => { server = app.listen(port, () => logger.log('Example app listening on port ' + port)) }
Listen: (port: number) => { server = app.listen(port, () => logger.log('LightningPub listening on port ' + port)) }
}
}

View file

@ -234,6 +234,17 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
EnrollAdminToken: async (request: Types.EnrollAdminTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveUserAuth()
if (auth === null) throw new Error('retrieveUserAuth() returned null')
let finalRoute = '/api/guest/npub/enroll/admin'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetApp: async (): Promise<ResultError | ({ status: 'OK' }& Types.Application)> => {
const auth = await params.retrieveAppAuth()
if (auth === null) throw new Error('retrieveAppAuth() returned null')

View file

@ -5,10 +5,116 @@ export type ResultError = { status: 'ERROR', reason: string }
export type NostrClientParams = {
pubDestination: string
retrieveNostrAdminAuth: () => Promise<string | null>
retrieveNostrMetricsAuth: () => Promise<string | null>
retrieveNostrUserAuth: () => Promise<string | null>
checkResult?: true
}
export default (params: NostrClientParams, send: (to:string, message: NostrRequest) => Promise<any>, subscribe: (to:string, message: NostrRequest, cb:(res:any)=> void) => void) => ({
LndGetInfo: async (request: Types.LndGetInfoRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndGetInfoResponse)> => {
const auth = await params.retrieveNostrAdminAuth()
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'LndGetInfo',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.LndGetInfoResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddApp: async (request: Types.AddAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AuthApp)> => {
const auth = await params.retrieveNostrAdminAuth()
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'AddApp',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.AuthAppValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
AuthApp: async (request: Types.AuthAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AuthApp)> => {
const auth = await params.retrieveNostrAdminAuth()
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'AuthApp',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.AuthAppValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
BanUser: async (request: Types.BanUserRequest): Promise<ResultError | ({ status: 'OK' }& Types.BanUserResponse)> => {
const auth = await params.retrieveNostrAdminAuth()
if (auth === null) throw new Error('retrieveNostrAdminAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'BanUser',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.BanUserResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetUsageMetrics: async (): Promise<ResultError | ({ status: 'OK' }& Types.UsageMetrics)> => {
const auth = await params.retrieveNostrMetricsAuth()
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
const nostrRequest: NostrRequest = {}
const data = await send(params.pubDestination, {rpcName:'GetUsageMetrics',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.UsageMetricsValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetAppsMetrics: async (request: Types.AppsMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.AppsMetrics)> => {
const auth = await params.retrieveNostrMetricsAuth()
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'GetAppsMetrics',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.AppsMetricsValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetLndMetrics: async (request: Types.LndMetricsRequest): Promise<ResultError | ({ status: 'OK' }& Types.LndMetrics)> => {
const auth = await params.retrieveNostrMetricsAuth()
if (auth === null) throw new Error('retrieveNostrMetricsAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'GetLndMetrics',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.LndMetricsValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
LinkNPubThroughToken: async (request: Types.LinkNPubThroughTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
@ -21,6 +127,18 @@ export default (params: NostrClientParams, send: (to:string, message: NostrRequ
}
return { status: 'ERROR', reason: 'invalid response' }
},
EnrollAdminToken: async (request: Types.EnrollAdminTokenRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')
const nostrRequest: NostrRequest = {}
nostrRequest.body = request
const data = await send(params.pubDestination, {rpcName:'EnrollAdminToken',authIdentifier:auth, ...nostrRequest })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
UserHealth: async (): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveNostrUserAuth()
if (auth === null) throw new Error('retrieveNostrUserAuth() returned null')

View file

@ -16,6 +16,8 @@ export type NostrOptions = {
logger?: Logger
throwErrors?: true
metricsCallback: (metrics: Types.RequestMetric[]) => void
NostrAdminAuthGuard: (appId?:string, identifier?: string) => Promise<Types.AdminContext>
NostrMetricsAuthGuard: (appId?:string, identifier?: string) => Promise<Types.MetricsContext>
NostrUserAuthGuard: (appId?:string, identifier?: string) => Promise<Types.UserContext>
}
const logErrorAndReturnResponse = (error: Error, response: string, res: NostrResponse, logger: Logger, metric: Types.RequestMetric, metricsCallback: (metrics: Types.RequestMetric[]) => void) => {
@ -29,6 +31,115 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
const stats: Types.RequestStats = { startMs, start: startTime, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
switch (req.rpcName) {
case 'LndGetInfo':
try {
if (!methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.LndGetInfoRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.LndGetInfo({rpcName:'LndGetInfo', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'AddApp':
try {
if (!methods.AddApp) throw new Error('method: AddApp is not implemented')
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.AddAppRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.AddApp({rpcName:'AddApp', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'AuthApp':
try {
if (!methods.AuthApp) throw new Error('method: AuthApp is not implemented')
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.AuthAppRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.AuthApp({rpcName:'AuthApp', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'BanUser':
try {
if (!methods.BanUser) throw new Error('method: BanUser is not implemented')
const authContext = await opts.NostrAdminAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.BanUserRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.BanUser({rpcName:'BanUser', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'GetUsageMetrics':
try {
if (!methods.GetUsageMetrics) throw new Error('method: GetUsageMetrics is not implemented')
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
stats.validate = stats.guard
const response = await methods.GetUsageMetrics({rpcName:'GetUsageMetrics', ctx:authContext })
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'GetAppsMetrics':
try {
if (!methods.GetAppsMetrics) throw new Error('method: GetAppsMetrics is not implemented')
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.AppsMetricsRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.GetAppsMetrics({rpcName:'GetAppsMetrics', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'GetLndMetrics':
try {
if (!methods.GetLndMetrics) throw new Error('method: GetLndMetrics is not implemented')
const authContext = await opts.NostrMetricsAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.LndMetricsRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
const response = await methods.GetLndMetrics({rpcName:'GetLndMetrics', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'LinkNPubThroughToken':
try {
if (!methods.LinkNPubThroughToken) throw new Error('method: LinkNPubThroughToken is not implemented')
@ -45,6 +156,22 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'EnrollAdminToken':
try {
if (!methods.EnrollAdminToken) throw new Error('method: EnrollAdminToken is not implemented')
const authContext = await opts.NostrUserAuthGuard(req.appId, req.authIdentifier)
stats.guard = process.hrtime.bigint()
authCtx = authContext
const request = req.body
const error = Types.EnrollAdminTokenRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback)
await methods.EnrollAdminToken({rpcName:'EnrollAdminToken', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
}catch(ex){ const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
break
case 'UserHealth':
try {
if (!methods.UserHealth) throw new Error('method: UserHealth is not implemented')
@ -321,6 +448,18 @@ export default (methods: Types.ServerMethods, opts: NostrOptions) => {
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'EnrollAdminToken':
if (!methods.EnrollAdminToken) {
throw new Error('method not defined: EnrollAdminToken')
} else {
const error = Types.EnrollAdminTokenRequestValidate(operation.req)
opStats.validate = process.hrtime.bigint()
if (error !== null) throw error
await methods.EnrollAdminToken({...operation, ctx}); responses.push({ status: 'OK' })
opStats.handle = process.hrtime.bigint()
callsMetrics.push({ ...opInfo, ...opStats, ...ctx })
}
break
case 'UserHealth':
if (!methods.UserHealth) {
throw new Error('method not defined: UserHealth')

File diff suppressed because it is too large Load diff

Binary file not shown.

BIN
proto/protoc-gen-pub_old Executable file

Binary file not shown.

View file

@ -92,42 +92,49 @@ service LightningPub {
option (auth_type) = "Admin";
option (http_method) = "post";
option (http_route) = "/api/admin/lnd/getinfo";
option (nostr) = true;
};
rpc AddApp(structs.AddAppRequest) returns (structs.AuthApp) {
option (auth_type) = "Admin";
option (http_method) = "post";
option (http_route) = "/api/admin/app/add";
option (nostr) = true;
};
rpc AuthApp(structs.AuthAppRequest) returns (structs.AuthApp) {
option (auth_type) = "Admin";
option (http_method) = "post";
option (http_route) = "/api/admin/app/auth";
option (nostr) = true;
}
rpc BanUser(structs.BanUserRequest) returns (structs.BanUserResponse) {
option (auth_type) = "Admin";
option (http_method) = "post";
option (http_route) = "/api/admin/user/ban";
option (nostr) = true;
}
rpc GetUsageMetrics(structs.Empty) returns (structs.UsageMetrics) {
option (auth_type) = "Metrics";
option (http_method) = "post";
option (http_route) = "/api/reports/usage";
option (nostr) = true;
}
rpc GetAppsMetrics(structs.AppsMetricsRequest) returns (structs.AppsMetrics) {
option (auth_type) = "Metrics";
option (http_method) = "post";
option (http_route) = "/api/reports/apps";
option (nostr) = true;
}
rpc GetLndMetrics(structs.LndMetricsRequest) returns (structs.LndMetrics) {
option (auth_type) = "Metrics";
option (http_method) = "post";
option (http_route) = "/api/reports/lnd";
option (nostr) = true;
}
@ -186,6 +193,12 @@ service LightningPub {
option (http_route) = "/api/guest/npub/link";
option (nostr) = true;
}
rpc EnrollAdminToken(structs.EnrollAdminTokenRequest) returns (structs.Empty) {
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/guest/npub/enroll/admin";
option (nostr) = true;
}
//</Guest>
// <App>

View file

@ -450,3 +450,7 @@ message HttpCreds {
string url = 1;
string token = 2;
}
message EnrollAdminTokenRequest {
string admin_token = 1;
}

View file

@ -51,15 +51,20 @@ service Wizard {
option (http_method) = "get";
option (http_route) = "/wizard/state";
};
rpc WizardConfig(wizard_structs.ConfigRequest) returns (wizard_structs.ConfigResponse){
rpc WizardConfig(wizard_structs.ConfigRequest) returns (wizard_structs.Empty){
option (auth_type) = "Guest";
option (http_method) = "post";
option (http_route) = "/wizard/config";
};
rpc WizardConfirm(wizard_structs.ConfirmRequest) returns (wizard_structs.ConfirmResponse){
rpc GetAdminConnectInfo(wizard_structs.Empty) returns (wizard_structs.AdminConnectInfoResponse){
option (auth_type) = "Guest";
option (http_method) = "post";
option (http_route) = "/wizard/confirm";
option (http_method) = "get";
option (http_route) = "/wizard/admin_connect_info";
};
rpc GetServiceState(wizard_structs.Empty) returns (wizard_structs.ServiceStateResponse){
option (auth_type) = "Guest";
option (http_method) = "get";
option (http_route) = "/wizard/service_state";
};
// </Guest>
}

View file

@ -7,7 +7,8 @@ option go_package = "github.com/shocknet/lightning.pub";
message Empty {}
message StateResponse {
bool already_initialized = 1;
bool config_sent = 1;
bool admin_linked = 2;
}
message ConfigRequest {
string source_name = 1;
@ -15,18 +16,25 @@ message ConfigRequest {
bool automate_liquidity = 3;
bool push_backups_to_nostr = 4;
}
message ConfigResponse {
bool already_initialized = 1;
repeated string seed = 2;
string confirmation_id = 3;
message AdminConnectInfoResponse {
string nprofile = 1;
oneof connect_info {
string admin_token = 2;
string enrolled_npub = 3;
}
message ConfirmRequest {
string confirmation_id = 1;
}
message ConfirmResponse {
string admin_key = 1;
enum LndState {
OFFLINE = 0;
SYNCING = 1;
ONLINE = 2;
}
message ServiceStateResponse {
string provider_name = 1;
repeated string relays = 2;
string admin_npub = 3;
bool relay_connected = 4;
LndState lnd_state = 5;
bool watchdog_ok = 6;
string http_url = 7;
string nprofile = 8;
}

View file

@ -36,27 +36,30 @@ The nostr server will send back a message response, and inside the body there wi
- http method: __post__
- http route: __/wizard/config__
- input: [ConfigRequest](#ConfigRequest)
- output: [ConfigResponse](#ConfigResponse)
- This methods has an __empty__ __response__ body
- WizardConfirm
- GetAdminConnectInfo
- auth type: __Guest__
- http method: __post__
- http route: __/wizard/confirm__
- input: [ConfirmRequest](#ConfirmRequest)
- output: [ConfirmResponse](#ConfirmResponse)
- http method: __get__
- http route: __/wizard/admin_connect_info__
- This methods has an __empty__ __request__ body
- output: [AdminConnectInfoResponse](#AdminConnectInfoResponse)
- GetServiceState
- auth type: __Guest__
- http method: __get__
- http route: __/wizard/service_state__
- This methods has an __empty__ __request__ body
- output: [ServiceStateResponse](#ServiceStateResponse)
# INPUTS AND OUTPUTS
## Messages
### The content of requests and response from the methods
### ConfirmResponse
- __admin_key__: _string_
### Empty
### StateResponse
- __already_initialized__: _boolean_
- __config_sent__: _boolean_
- __admin_linked__: _boolean_
### ConfigRequest
- __source_name__: _string_
@ -64,12 +67,25 @@ The nostr server will send back a message response, and inside the body there wi
- __automate_liquidity__: _boolean_
- __push_backups_to_nostr__: _boolean_
### ConfigResponse
- __already_initialized__: _boolean_
- __seed__: ARRAY of: _string_
- __confirmation_id__: _string_
### AdminConnectInfoResponse
- __nprofile__: _string_
- __connect_info__: _AdminConnectInfoResponse_connect_info_
### ConfirmRequest
- __confirmation_id__: _string_
### ServiceStateResponse
- __http_url__: _string_
- __nprofile__: _string_
- __provider_name__: _string_
- __relays__: ARRAY of: _string_
- __admin_npub__: _string_
- __relay_connected__: _boolean_
- __lnd_state__: _[LndState](#LndState)_
- __watchdog_ok__: _boolean_
### Empty
## Enums
### The enumerators used in the messages
### LndState
- __OFFLINE__
- __SYNCING__
- __ONLINE__

View file

@ -1,5 +1,5 @@
([]*main.Method) (len=3 cap=4) {
(*main.Method)(0xc0002221e0)({
([]*main.Method) (len=4 cap=4) {
(*main.Method)(0xc00022a280)({
in: (main.MethodMessage) {
name: (string) (len=5) "Empty",
hasZeroFields: (bool) true
@ -9,8 +9,8 @@
name: (string) (len=13) "StateResponse",
hasZeroFields: (bool) false
},
opts: (*main.methodOptions)(0xc00009e6c0)({
authType: (*main.supportedAuth)(0xc0003bd290)({
opts: (*main.methodOptions)(0xc00009a9c0)({
authType: (*main.supportedAuth)(0xc0003c9aa0)({
id: (string) (len=5) "guest",
name: (string) (len=5) "Guest",
context: (map[string]string) {
@ -27,18 +27,18 @@
}),
serverStream: (bool) false
}),
(*main.Method)(0xc000222230)({
(*main.Method)(0xc00022a2d0)({
in: (main.MethodMessage) {
name: (string) (len=13) "ConfigRequest",
hasZeroFields: (bool) false
},
name: (string) (len=12) "WizardConfig",
out: (main.MethodMessage) {
name: (string) (len=14) "ConfigResponse",
hasZeroFields: (bool) false
name: (string) (len=5) "Empty",
hasZeroFields: (bool) true
},
opts: (*main.methodOptions)(0xc00009e840)({
authType: (*main.supportedAuth)(0xc0003bd350)({
opts: (*main.methodOptions)(0xc00009ab40)({
authType: (*main.supportedAuth)(0xc0003c9b60)({
id: (string) (len=5) "guest",
name: (string) (len=5) "Guest",
context: (map[string]string) {
@ -55,26 +55,54 @@
}),
serverStream: (bool) false
}),
(*main.Method)(0xc0002225a0)({
(*main.Method)(0xc00022a640)({
in: (main.MethodMessage) {
name: (string) (len=14) "ConfirmRequest",
hasZeroFields: (bool) false
name: (string) (len=5) "Empty",
hasZeroFields: (bool) true
},
name: (string) (len=13) "WizardConfirm",
name: (string) (len=19) "GetAdminConnectInfo",
out: (main.MethodMessage) {
name: (string) (len=15) "ConfirmResponse",
name: (string) (len=24) "AdminConnectInfoResponse",
hasZeroFields: (bool) false
},
opts: (*main.methodOptions)(0xc00009e9c0)({
authType: (*main.supportedAuth)(0xc0003bd410)({
opts: (*main.methodOptions)(0xc00009acc0)({
authType: (*main.supportedAuth)(0xc0003c9c20)({
id: (string) (len=5) "guest",
name: (string) (len=5) "Guest",
context: (map[string]string) {
}
}),
method: (string) (len=4) "post",
method: (string) (len=3) "get",
route: (main.decodedRoute) {
route: (string) (len=15) "/wizard/confirm",
route: (string) (len=26) "/wizard/admin_connect_info",
params: ([]string) <nil>
},
query: ([]string) <nil>,
nostr: (bool) false,
batch: (bool) false
}),
serverStream: (bool) false
}),
(*main.Method)(0xc00022a690)({
in: (main.MethodMessage) {
name: (string) (len=5) "Empty",
hasZeroFields: (bool) true
},
name: (string) (len=15) "GetServiceState",
out: (main.MethodMessage) {
name: (string) (len=20) "ServiceStateResponse",
hasZeroFields: (bool) false
},
opts: (*main.methodOptions)(0xc00009ae40)({
authType: (*main.supportedAuth)(0xc0003c9ce0)({
id: (string) (len=5) "guest",
name: (string) (len=5) "Guest",
context: (map[string]string) {
}
}),
method: (string) (len=3) "get",
route: (main.decodedRoute) {
route: (string) (len=21) "/wizard/service_state",
params: ([]string) <nil>
},
query: ([]string) <nil>,
@ -85,137 +113,247 @@
})
}
([]*main.Enum) <nil>
([]*main.Enum) (len=1 cap=1) {
(*main.Enum)(0xc0003c9680)({
name: (string) (len=8) "LndState",
values: ([]main.EnumValue) (len=3 cap=4) {
(main.EnumValue) {
number: (int64) 0,
name: (string) (len=7) "OFFLINE"
},
(main.EnumValue) {
number: (int64) 1,
name: (string) (len=7) "SYNCING"
},
(main.EnumValue) {
number: (int64) 2,
name: (string) (len=6) "ONLINE"
}
}
})
}
(map[string]*main.Message) (len=6) {
(string) (len=13) "StateResponse": (*main.Message)(0xc0003ee1c0)({
(map[string]*main.Message) (len=5) {
(string) (len=5) "Empty": (*main.Message)(0xc0003c94a0)({
fullName: (string) (len=5) "Empty",
name: (string) (len=5) "Empty",
fields: (map[string]*main.Field) {
}
}),
(string) (len=13) "StateResponse": (*main.Message)(0xc0003c9500)({
fullName: (string) (len=13) "StateResponse",
name: (string) (len=13) "StateResponse",
fields: ([]*main.Field) (len=1 cap=1) {
(*main.Field)(0xc0003bccc0)({
name: (string) (len=19) "already_initialized",
fields: (map[string]*main.Field) (len=2) {
(string) (len=11) "config_sent": (*main.Field)(0xc0003ee440)({
name: (string) (len=11) "config_sent",
kind: (string) (len=4) "bool",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
})
}
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=13) "ConfigRequest": (*main.Message)(0xc0003ee200)({
fullName: (string) (len=13) "ConfigRequest",
name: (string) (len=13) "ConfigRequest",
fields: ([]*main.Field) (len=4 cap=4) {
(*main.Field)(0xc0003bccf0)({
name: (string) (len=11) "source_name",
kind: (string) (len=6) "string",
(string) (len=12) "admin_linked": (*main.Field)(0xc0003ee480)({
name: (string) (len=12) "admin_linked",
kind: (string) (len=4) "bool",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
isOptional: (bool) false,
oneOfName: (string) ""
})
}
}),
(*main.Field)(0xc0003bcd20)({
(string) (len=13) "ConfigRequest": (*main.Message)(0xc0003c9560)({
fullName: (string) (len=13) "ConfigRequest",
name: (string) (len=13) "ConfigRequest",
fields: (map[string]*main.Field) (len=4) {
(string) (len=9) "relay_url": (*main.Field)(0xc0003ee500)({
name: (string) (len=9) "relay_url",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
isOptional: (bool) false,
oneOfName: (string) ""
}),
(*main.Field)(0xc0003bcd50)({
(string) (len=18) "automate_liquidity": (*main.Field)(0xc0003ee540)({
name: (string) (len=18) "automate_liquidity",
kind: (string) (len=4) "bool",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
isOptional: (bool) false,
oneOfName: (string) ""
}),
(*main.Field)(0xc0003bcd80)({
(string) (len=21) "push_backups_to_nostr": (*main.Field)(0xc0003ee580)({
name: (string) (len=21) "push_backups_to_nostr",
kind: (string) (len=4) "bool",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=11) "source_name": (*main.Field)(0xc0003ee4c0)({
name: (string) (len=11) "source_name",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) ""
})
}
}),
(string) (len=14) "ConfigResponse": (*main.Message)(0xc0003ee240)({
fullName: (string) (len=14) "ConfigResponse",
name: (string) (len=14) "ConfigResponse",
fields: ([]*main.Field) (len=3 cap=4) {
(*main.Field)(0xc0003bcdb0)({
name: (string) (len=19) "already_initialized",
(string) (len=24) "AdminConnectInfoResponse": (*main.Message)(0xc0003c95c0)({
fullName: (string) (len=24) "AdminConnectInfoResponse",
name: (string) (len=24) "AdminConnectInfoResponse",
fields: (map[string]*main.Field) (len=2) {
(string) (len=8) "nprofile": (*main.Field)(0xc0003ee5c0)({
name: (string) (len=8) "nprofile",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=37) "AdminConnectInfoResponse_connect_info": (*main.Field)(0xc0003eea80)({
name: (string) (len=12) "connect_info",
kind: (string) (len=37) "AdminConnectInfoResponse_connect_info",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) (len=12) "connect_info"
})
}
}),
(string) (len=20) "ServiceStateResponse": (*main.Message)(0xc0003c9620)({
fullName: (string) (len=20) "ServiceStateResponse",
name: (string) (len=20) "ServiceStateResponse",
fields: (map[string]*main.Field) (len=8) {
(string) (len=10) "admin_npub": (*main.Field)(0xc0003ee700)({
name: (string) (len=10) "admin_npub",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=15) "relay_connected": (*main.Field)(0xc0003ee740)({
name: (string) (len=15) "relay_connected",
kind: (string) (len=4) "bool",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
isOptional: (bool) false,
oneOfName: (string) ""
}),
(*main.Field)(0xc0003bcde0)({
name: (string) (len=4) "seed",
(string) (len=9) "lnd_state": (*main.Field)(0xc0003ee780)({
name: (string) (len=9) "lnd_state",
kind: (string) (len=8) "LndState",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) true,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=11) "watchdog_ok": (*main.Field)(0xc0003ee7c0)({
name: (string) (len=11) "watchdog_ok",
kind: (string) (len=4) "bool",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=8) "http_url": (*main.Field)(0xc0003ee800)({
name: (string) (len=8) "http_url",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=8) "nprofile": (*main.Field)(0xc0003ee840)({
name: (string) (len=8) "nprofile",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=13) "provider_name": (*main.Field)(0xc0003ee680)({
name: (string) (len=13) "provider_name",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) ""
}),
(string) (len=6) "relays": (*main.Field)(0xc0003ee6c0)({
name: (string) (len=6) "relays",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) true,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
}),
(*main.Field)(0xc0003bce10)({
name: (string) (len=15) "confirmation_id",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
isOptional: (bool) false,
oneOfName: (string) ""
})
}
}),
(string) (len=14) "ConfirmRequest": (*main.Message)(0xc0003ee280)({
fullName: (string) (len=14) "ConfirmRequest",
name: (string) (len=14) "ConfirmRequest",
fields: ([]*main.Field) (len=1 cap=1) {
(*main.Field)(0xc0003bce40)({
name: (string) (len=15) "confirmation_id",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
})
}
}),
(string) (len=15) "ConfirmResponse": (*main.Message)(0xc0003ee2c0)({
fullName: (string) (len=15) "ConfirmResponse",
name: (string) (len=15) "ConfirmResponse",
fields: ([]*main.Field) (len=1 cap=1) {
(*main.Field)(0xc0003bce70)({
name: (string) (len=9) "admin_key",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false
})
}
}),
(string) (len=5) "Empty": (*main.Message)(0xc0003ee080)({
fullName: (string) (len=5) "Empty",
name: (string) (len=5) "Empty",
fields: ([]*main.Field) <nil>
})
}
parsing file: wizard_structs 6
(map[string][]*main.Field) (len=1) {
(string) (len=37) "AdminConnectInfoResponse_connect_info": ([]*main.Field) (len=2 cap=2) {
(*main.Field)(0xc0003ee600)({
name: (string) (len=11) "admin_token",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) (len=12) "connect_info"
}),
(*main.Field)(0xc0003ee640)({
name: (string) (len=13) "enrolled_npub",
kind: (string) (len=6) "string",
isMap: (bool) false,
isArray: (bool) false,
isEnum: (bool) false,
isMessage: (bool) false,
isOptional: (bool) false,
oneOfName: (string) (len=12) "connect_info"
})
}
}
parsing file: wizard_structs 5
parsing file: wizard_methods 2
-> [{guest Guest map[]}]
([]interface {}) <nil>

View file

@ -64,29 +64,45 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
const query = req.query
const params = req.params
const response = await methods.WizardConfig({rpcName:'WizardConfig', ctx:authContext , req: request})
await methods.WizardConfig({rpcName:'WizardConfig', ctx:authContext , req: request})
stats.handle = process.hrtime.bigint()
res.json({status: 'OK'})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetAdminConnectInfo) throw new Error('method: GetAdminConnectInfo is not implemented')
app.get('/wizard/admin_connect_info', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetAdminConnectInfo', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.GetAdminConnectInfo) throw new Error('method: GetAdminConnectInfo is not implemented')
const authContext = await opts.GuestAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
stats.validate = stats.guard
const query = req.query
const params = req.params
const response = await methods.GetAdminConnectInfo({rpcName:'GetAdminConnectInfo', ctx:authContext })
stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger, { ...info, ...stats, ...authCtx }, opts.metricsCallback); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.WizardConfirm) throw new Error('method: WizardConfirm is not implemented')
app.post('/wizard/confirm', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'WizardConfirm', batch: false, nostr: false, batchSize: 0}
if (!opts.allowNotImplementedMethods && !methods.GetServiceState) throw new Error('method: GetServiceState is not implemented')
app.get('/wizard/service_state', async (req, res) => {
const info: Types.RequestInfo = { rpcName: 'GetServiceState', batch: false, nostr: false, batchSize: 0}
const stats: Types.RequestStats = { startMs:req.startTimeMs || 0, start:req.startTime || 0n, parse: process.hrtime.bigint(), guard: 0n, validate: 0n, handle: 0n }
let authCtx: Types.AuthContext = {}
try {
if (!methods.WizardConfirm) throw new Error('method: WizardConfirm is not implemented')
if (!methods.GetServiceState) throw new Error('method: GetServiceState is not implemented')
const authContext = await opts.GuestAuthGuard(req.headers['authorization'])
authCtx = authContext
stats.guard = process.hrtime.bigint()
const request = req.body
const error = Types.ConfirmRequestValidate(request)
stats.validate = process.hrtime.bigint()
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger, { ...info, ...stats, ...authContext }, opts.metricsCallback)
stats.validate = stats.guard
const query = req.query
const params = req.params
const response = await methods.WizardConfirm({rpcName:'WizardConfirm', ctx:authContext , req: request})
const response = await methods.GetServiceState({rpcName:'GetServiceState', ctx:authContext })
stats.handle = process.hrtime.bigint()
res.json({status: 'OK', ...response})
opts.metricsCallback([{ ...info, ...stats, ...authContext }])
@ -99,6 +115,6 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
var server: { close: () => void } | undefined
return {
Close: () => { if (!server) { throw new Error('tried closing server before starting') } else server.close() },
Listen: (port: number) => { server = app.listen(port, () => logger.log('Example app listening on port ' + port)) }
Listen: (port: number) => { server = app.listen(port, () => logger.log('Wizard listening on port ' + port)) }
}
}

View file

@ -26,30 +26,41 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
WizardConfig: async (request: Types.ConfigRequest): Promise<ResultError | ({ status: 'OK' }& Types.ConfigResponse)> => {
WizardConfig: async (request: Types.ConfigRequest): Promise<ResultError | ({ status: 'OK' })> => {
const auth = await params.retrieveGuestAuth()
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
let finalRoute = '/wizard/config'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.ConfigResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
return data
}
return { status: 'ERROR', reason: 'invalid response' }
},
WizardConfirm: async (request: Types.ConfirmRequest): Promise<ResultError | ({ status: 'OK' }& Types.ConfirmResponse)> => {
GetAdminConnectInfo: async (): Promise<ResultError | ({ status: 'OK' }& Types.AdminConnectInfoResponse)> => {
const auth = await params.retrieveGuestAuth()
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
let finalRoute = '/wizard/confirm'
const { data } = await axios.post(params.baseUrl + finalRoute, request, { headers: { 'authorization': auth } })
let finalRoute = '/wizard/admin_connect_info'
const { data } = await axios.get(params.baseUrl + finalRoute, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.ConfirmResponseValidate(result)
const error = Types.AdminConnectInfoResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }
},
GetServiceState: async (): Promise<ResultError | ({ status: 'OK' }& Types.ServiceStateResponse)> => {
const auth = await params.retrieveGuestAuth()
if (auth === null) throw new Error('retrieveGuestAuth() returned null')
let finalRoute = '/wizard/service_state'
const { data } = await axios.get(params.baseUrl + finalRoute, { headers: { 'authorization': auth } })
if (data.status === 'ERROR' && typeof data.reason === 'string') return data
if (data.status === 'OK') {
const result = data
if(!params.checkResult) return { status: 'OK', ...result }
const error = Types.ServiceStateResponseValidate(result)
if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message }
}
return { status: 'ERROR', reason: 'invalid response' }

View file

@ -6,97 +6,43 @@ export type RequestStats = { startMs:number, start:bigint, parse: bigint, guard:
export type RequestMetric = AuthContext & RequestInfo & RequestStats & { error?: string }
export type GuestContext = {
}
export type GuestMethodInputs = WizardState_Input | WizardConfig_Input | WizardConfirm_Input
export type GuestMethodOutputs = WizardState_Output | WizardConfig_Output | WizardConfirm_Output
export type GuestMethodInputs = WizardState_Input | WizardConfig_Input | GetAdminConnectInfo_Input | GetServiceState_Input
export type GuestMethodOutputs = WizardState_Output | WizardConfig_Output | GetAdminConnectInfo_Output | GetServiceState_Output
export type AuthContext = GuestContext
export type WizardState_Input = {rpcName:'WizardState'}
export type WizardState_Output = ResultError | ({ status: 'OK' } & StateResponse)
export type WizardConfig_Input = {rpcName:'WizardConfig', req: ConfigRequest}
export type WizardConfig_Output = ResultError | ({ status: 'OK' } & ConfigResponse)
export type WizardConfig_Output = ResultError | { status: 'OK' }
export type WizardConfirm_Input = {rpcName:'WizardConfirm', req: ConfirmRequest}
export type WizardConfirm_Output = ResultError | ({ status: 'OK' } & ConfirmResponse)
export type GetAdminConnectInfo_Input = {rpcName:'GetAdminConnectInfo'}
export type GetAdminConnectInfo_Output = ResultError | ({ status: 'OK' } & AdminConnectInfoResponse)
export type GetServiceState_Input = {rpcName:'GetServiceState'}
export type GetServiceState_Output = ResultError | ({ status: 'OK' } & ServiceStateResponse)
export type ServerMethods = {
WizardState?: (req: WizardState_Input & {ctx: GuestContext }) => Promise<StateResponse>
WizardConfig?: (req: WizardConfig_Input & {ctx: GuestContext }) => Promise<ConfigResponse>
WizardConfirm?: (req: WizardConfirm_Input & {ctx: GuestContext }) => Promise<ConfirmResponse>
WizardConfig?: (req: WizardConfig_Input & {ctx: GuestContext }) => Promise<void>
GetAdminConnectInfo?: (req: GetAdminConnectInfo_Input & {ctx: GuestContext }) => Promise<AdminConnectInfoResponse>
GetServiceState?: (req: GetServiceState_Input & {ctx: GuestContext }) => Promise<ServiceStateResponse>
}
export enum LndState {
OFFLINE = 'OFFLINE',
SYNCING = 'SYNCING',
ONLINE = 'ONLINE',
}
export const enumCheckLndState = (e?: LndState): boolean => {
for (const v in LndState) if (e === v) return true
return false
}
export type OptionsBaseMessage = {
allOptionalsAreSet?: true
}
export type ConfigResponse = {
already_initialized: boolean
seed: string[]
confirmation_id: string
}
export const ConfigResponseOptionalFields: [] = []
export type ConfigResponseOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
already_initialized_CustomCheck?: (v: boolean) => boolean
seed_CustomCheck?: (v: string[]) => boolean
confirmation_id_CustomCheck?: (v: string) => boolean
}
export const ConfigResponseValidate = (o?: ConfigResponse, opts: ConfigResponseOptions = {}, path: string = 'ConfigResponse::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.already_initialized !== 'boolean') return new Error(`${path}.already_initialized: is not a boolean`)
if (opts.already_initialized_CustomCheck && !opts.already_initialized_CustomCheck(o.already_initialized)) return new Error(`${path}.already_initialized: custom check failed`)
if (!Array.isArray(o.seed)) return new Error(`${path}.seed: is not an array`)
for (let index = 0; index < o.seed.length; index++) {
if (typeof o.seed[index] !== 'string') return new Error(`${path}.seed[${index}]: is not a string`)
}
if (opts.seed_CustomCheck && !opts.seed_CustomCheck(o.seed)) return new Error(`${path}.seed: custom check failed`)
if (typeof o.confirmation_id !== 'string') return new Error(`${path}.confirmation_id: is not a string`)
if (opts.confirmation_id_CustomCheck && !opts.confirmation_id_CustomCheck(o.confirmation_id)) return new Error(`${path}.confirmation_id: custom check failed`)
return null
}
export type ConfirmRequest = {
confirmation_id: string
}
export const ConfirmRequestOptionalFields: [] = []
export type ConfirmRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
confirmation_id_CustomCheck?: (v: string) => boolean
}
export const ConfirmRequestValidate = (o?: ConfirmRequest, opts: ConfirmRequestOptions = {}, path: string = 'ConfirmRequest::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.confirmation_id !== 'string') return new Error(`${path}.confirmation_id: is not a string`)
if (opts.confirmation_id_CustomCheck && !opts.confirmation_id_CustomCheck(o.confirmation_id)) return new Error(`${path}.confirmation_id: custom check failed`)
return null
}
export type ConfirmResponse = {
admin_key: string
}
export const ConfirmResponseOptionalFields: [] = []
export type ConfirmResponseOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
admin_key_CustomCheck?: (v: string) => boolean
}
export const ConfirmResponseValidate = (o?: ConfirmResponse, opts: ConfirmResponseOptions = {}, path: string = 'ConfirmResponse::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.admin_key !== 'string') return new Error(`${path}.admin_key: is not a string`)
if (opts.admin_key_CustomCheck && !opts.admin_key_CustomCheck(o.admin_key)) return new Error(`${path}.admin_key: custom check failed`)
return null
}
export type Empty = {
}
export const EmptyOptionalFields: [] = []
@ -111,19 +57,24 @@ export const EmptyValidate = (o?: Empty, opts: EmptyOptions = {}, path: string =
}
export type StateResponse = {
already_initialized: boolean
config_sent: boolean
admin_linked: boolean
}
export const StateResponseOptionalFields: [] = []
export type StateResponseOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
already_initialized_CustomCheck?: (v: boolean) => boolean
config_sent_CustomCheck?: (v: boolean) => boolean
admin_linked_CustomCheck?: (v: boolean) => boolean
}
export const StateResponseValidate = (o?: StateResponse, opts: StateResponseOptions = {}, path: string = 'StateResponse::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.already_initialized !== 'boolean') return new Error(`${path}.already_initialized: is not a boolean`)
if (opts.already_initialized_CustomCheck && !opts.already_initialized_CustomCheck(o.already_initialized)) return new Error(`${path}.already_initialized: custom check failed`)
if (typeof o.config_sent !== 'boolean') return new Error(`${path}.config_sent: is not a boolean`)
if (opts.config_sent_CustomCheck && !opts.config_sent_CustomCheck(o.config_sent)) return new Error(`${path}.config_sent: custom check failed`)
if (typeof o.admin_linked !== 'boolean') return new Error(`${path}.admin_linked: is not a boolean`)
if (opts.admin_linked_CustomCheck && !opts.admin_linked_CustomCheck(o.admin_linked)) return new Error(`${path}.admin_linked: custom check failed`)
return null
}
@ -137,10 +88,10 @@ export type ConfigRequest = {
export const ConfigRequestOptionalFields: [] = []
export type ConfigRequestOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
push_backups_to_nostr_CustomCheck?: (v: boolean) => boolean
source_name_CustomCheck?: (v: string) => boolean
relay_url_CustomCheck?: (v: string) => boolean
automate_liquidity_CustomCheck?: (v: boolean) => boolean
push_backups_to_nostr_CustomCheck?: (v: boolean) => boolean
}
export const ConfigRequestValidate = (o?: ConfigRequest, opts: ConfigRequestOptions = {}, path: string = 'ConfigRequest::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
@ -161,3 +112,103 @@ export const ConfigRequestValidate = (o?: ConfigRequest, opts: ConfigRequestOpti
return null
}
export type AdminConnectInfoResponse = {
nprofile: string
connect_info: AdminConnectInfoResponse_connect_info
}
export const AdminConnectInfoResponseOptionalFields: [] = []
export type AdminConnectInfoResponseOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
nprofile_CustomCheck?: (v: string) => boolean
connect_info_CustomCheck?: (v: AdminConnectInfoResponse_connect_info) => boolean
}
export const AdminConnectInfoResponseValidate = (o?: AdminConnectInfoResponse, opts: AdminConnectInfoResponseOptions = {}, path: string = 'AdminConnectInfoResponse::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (typeof o.nprofile !== 'string') return new Error(`${path}.nprofile: is not a string`)
if (opts.nprofile_CustomCheck && !opts.nprofile_CustomCheck(o.nprofile)) return new Error(`${path}.nprofile: custom check failed`)
const connect_infoErr = AdminConnectInfoResponse_connect_infoValidate(o.connect_info,{}, `${path}.connect_info`)
if (connect_infoErr !== null) return connect_infoErr
return null
}
export type ServiceStateResponse = {
http_url: string
nprofile: string
provider_name: string
relays: string[]
admin_npub: string
relay_connected: boolean
lnd_state: LndState
watchdog_ok: boolean
}
export const ServiceStateResponseOptionalFields: [] = []
export type ServiceStateResponseOptions = OptionsBaseMessage & {
checkOptionalsAreSet?: []
http_url_CustomCheck?: (v: string) => boolean
nprofile_CustomCheck?: (v: string) => boolean
provider_name_CustomCheck?: (v: string) => boolean
relays_CustomCheck?: (v: string[]) => boolean
admin_npub_CustomCheck?: (v: string) => boolean
relay_connected_CustomCheck?: (v: boolean) => boolean
lnd_state_CustomCheck?: (v: LndState) => boolean
watchdog_ok_CustomCheck?: (v: boolean) => boolean
}
export const ServiceStateResponseValidate = (o?: ServiceStateResponse, opts: ServiceStateResponseOptions = {}, path: string = 'ServiceStateResponse::root.'): Error | null => {
if (opts.checkOptionalsAreSet && opts.allOptionalsAreSet) return new Error(path + ': only one of checkOptionalsAreSet or allOptionalNonDefault can be set for each message')
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
if (!Array.isArray(o.relays)) return new Error(`${path}.relays: is not an array`)
for (let index = 0; index < o.relays.length; index++) {
if (typeof o.relays[index] !== 'string') return new Error(`${path}.relays[${index}]: is not a string`)
}
if (opts.relays_CustomCheck && !opts.relays_CustomCheck(o.relays)) return new Error(`${path}.relays: custom check failed`)
if (typeof o.admin_npub !== 'string') return new Error(`${path}.admin_npub: is not a string`)
if (opts.admin_npub_CustomCheck && !opts.admin_npub_CustomCheck(o.admin_npub)) return new Error(`${path}.admin_npub: custom check failed`)
if (typeof o.relay_connected !== 'boolean') return new Error(`${path}.relay_connected: is not a boolean`)
if (opts.relay_connected_CustomCheck && !opts.relay_connected_CustomCheck(o.relay_connected)) return new Error(`${path}.relay_connected: custom check failed`)
if (!enumCheckLndState(o.lnd_state)) return new Error(`${path}.lnd_state: is not a valid LndState`)
if (opts.lnd_state_CustomCheck && !opts.lnd_state_CustomCheck(o.lnd_state)) return new Error(`${path}.lnd_state: custom check failed`)
if (typeof o.watchdog_ok !== 'boolean') return new Error(`${path}.watchdog_ok: is not a boolean`)
if (opts.watchdog_ok_CustomCheck && !opts.watchdog_ok_CustomCheck(o.watchdog_ok)) return new Error(`${path}.watchdog_ok: custom check failed`)
if (typeof o.http_url !== 'string') return new Error(`${path}.http_url: is not a string`)
if (opts.http_url_CustomCheck && !opts.http_url_CustomCheck(o.http_url)) return new Error(`${path}.http_url: custom check failed`)
if (typeof o.nprofile !== 'string') return new Error(`${path}.nprofile: is not a string`)
if (opts.nprofile_CustomCheck && !opts.nprofile_CustomCheck(o.nprofile)) return new Error(`${path}.nprofile: custom check failed`)
if (typeof o.provider_name !== 'string') return new Error(`${path}.provider_name: is not a string`)
if (opts.provider_name_CustomCheck && !opts.provider_name_CustomCheck(o.provider_name)) return new Error(`${path}.provider_name: custom check failed`)
return null
}
export enum AdminConnectInfoResponse_connect_info_type {
ADMIN_TOKEN = 'admin_token',
ENROLLED_NPUB = 'enrolled_npub',
}
export type AdminConnectInfoResponse_connect_info =
{type:AdminConnectInfoResponse_connect_info_type.ADMIN_TOKEN, admin_token:string}|
{type:AdminConnectInfoResponse_connect_info_type.ENROLLED_NPUB, enrolled_npub:string}
export const AdminConnectInfoResponse_connect_infoValidate = (o?: AdminConnectInfoResponse_connect_info, opts = {}, path: string = 'AdminConnectInfoResponse_connect_info::root.'): Error | null => {
if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null')
switch (o.type) {
case 'admin_token':
if (typeof o.admin_token !== 'string') return new Error(`${path}.admin_token: is not a string`)
break
case 'enrolled_npub':
if (typeof o.enrolled_npub !== 'string') return new Error(`${path}.enrolled_npub: is not a string`)
break
}
return new Error(path + ': unknown type'+ o.type)
}

View file

@ -7,6 +7,7 @@ import nostrMiddleware from './nostrMiddleware.js'
import { getLogger } from './services/helpers/logger.js';
import { initMainHandler } from './services/main/init.js';
import { LoadMainSettingsFromEnv } from './services/main/settings.js';
import { encodeNprofile } from './custom-nip19.js';
const start = async () => {
const log = getLogger({})
@ -16,7 +17,7 @@ const start = async () => {
log("manual process ended")
return
}
const { apps, mainHandler, liquidityProviderInfo } = keepOn
const { apps, mainHandler, liquidityProviderInfo, wizard } = keepOn
const serverMethods = GetServerMethods(mainHandler)
const nostrSettings = LoadNosrtSettingsFromEnv()
log("initializing nostr middleware")
@ -27,6 +28,9 @@ const start = async () => {
log("starting server")
mainHandler.attachNostrSend(Send)
mainHandler.StartBeacons()
if (wizard) {
wizard.AddConnectInfo(encodeNprofile({ pubkey: liquidityProviderInfo.publicKey, relays: nostrSettings.relays }), nostrSettings.relays)
}
const Server = NewServer(serverMethods, serverOptions(mainHandler))
Server.Listen(mainSettings.servicePort)
}

View file

@ -13,6 +13,20 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
const nostrUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, pub || "")
return { user_id: nostrUser.user.user_id, app_user_id: nostrUser.identifier, app_id: appId || "" }
},
NostrAdminAuthGuard: async (appId, pub) => {
const adminNpub = mainHandler.adminManager.GetAdminNpub()
if (!adminNpub) { throw new Error("admin access not configured") }
if (pub !== adminNpub) { throw new Error("admin access denied") }
log("admin access from", pub)
return { admin_id: pub }
},
NostrMetricsAuthGuard: async (appId, pub) => {
const adminNpub = mainHandler.adminManager.GetAdminNpub()
if (!adminNpub) { throw new Error("admin access not configured") }
if (pub !== adminNpub) { throw new Error("admin access denied") }
log("operator access from", pub)
return { operator_id: pub }
},
metricsCallback: metrics => mainHandler.settings.recordPerformance ? mainHandler.metricsManager.AddMetrics(metrics) : null,
logger: { log: console.log, error: err => log(ERROR, err) }
})

View file

@ -0,0 +1,97 @@
import fs, { watchFile } from "fs";
import crypto from 'crypto'
import { getLogger } from "../helpers/logger.js";
import { MainSettings, getDataPath } from "./settings.js";
import Storage from "../storage/index.js";
export class AdminManager {
storage: Storage
log = getLogger({ component: "adminManager" })
adminNpub = ""
dataDir: string
adminNpubPath: string
adminEnrollTokenPath: string
interval: NodeJS.Timer
constructor(mainSettings: MainSettings, storage: Storage) {
this.storage = storage
this.dataDir = mainSettings.storageSettings.dataDir
this.adminNpubPath = getDataPath(this.dataDir, 'admin.npub')
this.adminEnrollTokenPath = getDataPath(this.dataDir, '.admin_enroll')
this.start()
}
Stop = () => {
clearInterval(this.interval)
}
GenerateAdminEnrollToken = async () => {
const token = crypto.randomBytes(32).toString('hex')
fs.writeFileSync(this.adminEnrollTokenPath, token)
return token
}
start = () => {
const adminNpub = this.ReadAdminNpub()
if (adminNpub) {
this.adminNpub = adminNpub
} else if (!fs.existsSync(this.adminEnrollTokenPath)) {
this.GenerateAdminEnrollToken()
}
this.interval = setInterval(() => {
if (!this.adminNpub) {
return
}
const deleted = !fs.existsSync(this.adminNpubPath)
if (deleted) {
this.adminNpub = ""
this.log("admin npub file deleted")
this.GenerateAdminEnrollToken()
}
})
}
ReadAdminEnrollToken = () => {
try {
return fs.readFileSync(this.adminEnrollTokenPath, 'utf8').trim()
} catch (err: any) {
return ""
}
}
ReadAdminNpub = () => {
try {
return fs.readFileSync(this.adminNpubPath, 'utf8').trim()
} catch (err: any) {
return ""
}
}
GetAdminNpub = () => {
return this.adminNpub
}
ClearExistingAdmin = () => {
try {
fs.unlinkSync(this.adminNpubPath)
} catch (err: any) { }
}
PromoteUserToAdmin = async (appId: string, appUserId: string, token: string) => {
const app = await this.storage.applicationStorage.GetApplication(appId)
const appUser = await this.storage.applicationStorage.GetApplicationUser(app, appUserId)
const npub = appUser.nostr_public_key
if (!npub) {
throw new Error("no npub found for user")
}
let actualToken
try {
actualToken = fs.readFileSync(this.adminEnrollTokenPath, 'utf8').trim()
} catch (err: any) {
throw new Error("invalid enroll token")
}
if (token !== actualToken) {
throw new Error("invalid enroll token")
}
fs.writeFileSync(this.adminNpubPath, npub)
fs.unlinkSync(this.adminEnrollTokenPath)
this.adminNpub = npub
}
}

View file

@ -19,6 +19,7 @@ import { LiquidityProvider } from "./liquidityProvider.js"
import { LiquidityManager } from "./liquidityManager.js"
import { Utils } from "../helpers/utilsWrapper.js"
import { RugPullTracker } from "./rugPullTracker.js"
import { AdminManager } from "./adminManager.js"
type UserOperationsSub = {
id: string
@ -33,21 +34,23 @@ export default class {
lnd: LND
settings: MainSettings
userOperationsSub: UserOperationsSub | null = null
adminManager: AdminManager
productManager: ProductManager
applicationManager: ApplicationManager
appUserManager: AppUserManager
paymentManager: PaymentManager
paymentSubs: Record<string, ((op: Types.UserOperation) => void) | null> = {}
metricsManager: MetricsManager
liquidityProvider: LiquidityProvider
liquidityManager: LiquidityManager
liquidityProvider: LiquidityProvider
utils: Utils
rugPullTracker: RugPullTracker
nostrSend: NostrSend = () => { getLogger({})("nostr send not initialized yet") }
constructor(settings: MainSettings, storage: Storage, utils: Utils) {
constructor(settings: MainSettings, storage: Storage, adminManager: AdminManager, utils: Utils) {
this.settings = settings
this.storage = storage
this.utils = utils
this.adminManager = adminManager
const updateProviderBalance = (b: number) => this.storage.liquidityStorage.IncrementTrackedProviderBalance('lnPub', settings.liquiditySettings.liquidityProviderPub, b)
this.liquidityProvider = new LiquidityProvider(settings.liquiditySettings.liquidityProviderPub, this.utils, this.invoicePaidCb, updateProviderBalance)
this.rugPullTracker = new RugPullTracker(this.storage, this.liquidityProvider)

View file

@ -8,6 +8,8 @@ import SanityChecker from "./sanityChecker.js"
import { LoadMainSettingsFromEnv, MainSettings } from "./settings.js"
import { Utils } from "../helpers/utilsWrapper.js"
import { Wizard } from "../wizard/index.js"
import { AdminManager } from "./adminManager.js"
import { encodeNprofile } from "../../custom-nip19.js"
export type AppData = {
privateKey: string;
publicKey: string;
@ -21,19 +23,20 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
if (manualMigration) {
return
}
const unlocker = new Unlocker(mainSettings, storageManager)
await unlocker.Unlock()
const adminManager = new AdminManager(mainSettings, storageManager)
let reloadedSettings = mainSettings
let wizard: Wizard | null = null
if (mainSettings.wizard) {
const wizard = new Wizard(mainSettings, storageManager)
const reload = await wizard.WaitUntilConfigured()
wizard = new Wizard(mainSettings, storageManager, adminManager)
const reload = await wizard.Configure()
if (reload) {
reloadedSettings = LoadMainSettingsFromEnv()
}
} else {
const unlocker = new Unlocker(mainSettings, storageManager)
await unlocker.Unlock()
}
const mainHandler = new Main(reloadedSettings, storageManager, utils)
const mainHandler = new Main(reloadedSettings, storageManager, adminManager, utils)
await mainHandler.lnd.Warmup()
if (!reloadedSettings.skipSanityCheck) {
const sanityChecker = new SanityChecker(storageManager, mainHandler.lnd)
@ -70,7 +73,7 @@ export const initMainHandler = async (log: PubLogger, mainSettings: MainSettings
return
}
mainHandler.paymentManager.watchDog.Start()
return { mainHandler, apps, liquidityProviderInfo, liquidityProviderApp }
return { mainHandler, apps, liquidityProviderInfo, liquidityProviderApp, wizard }
}
const processArgs = async (mainHandler: Main) => {

View file

@ -50,7 +50,11 @@ export class LiquidityManager {
}
onNewBlock = async () => {
try {
await this.shouldDrainProvider()
} catch (err: any) {
this.log("error in onNewBlock", err.message || err)
}
}
beforeInvoiceCreation = async (amount: number): Promise<'lnd' | 'provider'> => {

View file

@ -43,6 +43,8 @@ export class LiquidityProvider {
this.client = newNostrClient({
pubDestination: this.pubDestination,
retrieveNostrUserAuth: async () => this.myPub,
retrieveNostrAdminAuth: async () => this.myPub,
retrieveNostrMetricsAuth: async () => this.myPub,
}, this.clientSend, this.clientSub)
this.configuredInterval = setInterval(() => {

View file

@ -7,6 +7,7 @@ import { getLogger } from '../helpers/logger.js'
import fs from 'fs'
import crypto from 'crypto';
import { LiquiditySettings, LoadLiquiditySettingsFromEnv } from './liquidityManager.js'
export type MainSettings = {
storageSettings: StorageSettings,
lndSettings: LndSettings,
@ -44,6 +45,7 @@ export type TestSettings = MainSettings & { lndSettings: { otherNode: NodeSettin
export const LoadMainSettingsFromEnv = (): MainSettings => {
const storageSettings = LoadStorageSettingsFromEnv()
const outgoingAppUserInvoiceFeeBps = EnvCanBeInteger("OUTGOING_INVOICE_FEE_USER_BPS", 0)
return {
watchDogSettings: LoadWatchdogSettingsFromEnv(),
lndSettings: LoadLndSettingsFromEnv(),

View file

@ -15,7 +15,7 @@ export class Unlocker {
settings: MainSettings
storage: Storage
abortController = new AbortController()
pendingSeed: Record<string, EncryptedData> = {}
subbedToBackups = false
log = getLogger({ component: "unlocker" })
constructor(settings: MainSettings, storage: Storage) {
this.settings = settings
@ -46,55 +46,29 @@ export class Unlocker {
return { lndCert, macaroon }
}
IsInitialized = async () => {
const { macaroon } = await this.getCreds()
IsInitialized = () => {
const { macaroon } = this.getCreds()
return macaroon !== ''
}
InitInteractive = async (): Promise<{ alreadyInitizialized: true } | { alreadyInitizialized: false, seed: string[], confirmationId: string }> => {
const { lndCert, macaroon } = await this.getCreds()
if (macaroon !== '') {
const { ln, pub } = await this.UnlockFlow(lndCert, macaroon)
Unlock = async (): Promise<'created' | 'unlocked' | 'noaction'> => {
const { lndCert, macaroon } = this.getCreds()
if (macaroon === "") {
const { ln, pub } = await this.InitFlow(lndCert)
this.subscribeToBackups(ln, pub)
return { alreadyInitizialized: true }
return 'created'
}
const unlocker = this.GetUnlockerClient(lndCert)
const seed = await this.genSeed(unlocker)
const confirmationId = crypto.randomBytes(32).toString('hex')
this.pendingSeed[confirmationId] = seed.encryptedSeed
return { alreadyInitizialized: false, seed: seed.plaintextSeed, confirmationId }
const { ln, pub, action } = await this.UnlockFlow(lndCert, macaroon)
this.subscribeToBackups(ln, pub)
return action
}
ConfirmInitInteractive = async (confirmationId: string) => {
const { lndCert, macaroon } = await this.getCreds()
if (macaroon !== '') {
const { ln, pub } = await this.UnlockFlow(lndCert, macaroon)
this.subscribeToBackups(ln, pub)
return { alreadyInitizialized: true }
}
const seed = this.pendingSeed[confirmationId]
if (!seed) {
throw new Error("seed not found")
}
delete this.pendingSeed[confirmationId]
const plaintextSeed = this.DecryptWalletSeed(seed)
const unlocker = this.GetUnlockerClient(lndCert)
const { ln, pub } = await this.initWallet(lndCert, unlocker, { plaintextSeed, encryptedSeed: seed })
this.subscribeToBackups(ln, pub)
}
Unlock = async () => {
const { lndCert, macaroon } = await this.getCreds()
const { ln, pub } = macaroon === "" ? await this.InitFlow(lndCert) : await this.UnlockFlow(lndCert, macaroon)
this.subscribeToBackups(ln, pub)
}
UnlockFlow = async (lndCert: Buffer, macaroon: string) => {
UnlockFlow = async (lndCert: Buffer, macaroon: string): Promise<{ ln: LightningClient, pub: string, action: 'unlocked' | 'noaction' }> => {
const ln = this.GetLightningClient(lndCert, macaroon)
const info = await this.GetLndInfo(ln)
if (info.ok) {
this.log("the wallet is already unlocked with pub:", info.pub)
return { ln, pub: info.pub }
return { ln, pub: info.pub, action: 'noaction' }
}
if (info.failure !== 'locked') {
throw new Error("failed to get lnd info for reason: " + info.failure)
@ -108,7 +82,7 @@ export class Unlocker {
throw new Error("failed to unlock lnd wallet " + infoAfter.failure)
}
this.log("unlocked wallet with pub:", infoAfter.pub)
return { ln, pub: infoAfter.pub }
return { ln, pub: infoAfter.pub, action: 'unlocked' }
}
InitFlow = async (lndCert: Buffer) => {
@ -254,6 +228,10 @@ export class Unlocker {
}
subscribeToBackups = async (ln: LightningClient, pub: string) => {
if (this.subbedToBackups) {
return
}
this.subbedToBackups = true
this.log("subscribing to channel backups for: ", pub)
const stream = ln.subscribeChannelBackups({}, { abort: this.abortController.signal })
stream.responses.onMessage(async (msg) => {

View file

@ -194,6 +194,7 @@ export default class Handler {
await p
sent = true
} catch (e: any) {
console.log(e)
log(e)
}
}))

View file

@ -214,6 +214,13 @@ export default (mainHandler: Main): Types.ServerMethods => {
})
if (err != null) throw new Error(err.message)
return mainHandler.applicationManager.LinkNpubThroughToken(ctx, req)
}
},
EnrollAdminToken: async ({ ctx, req }) => {
const err = Types.EnrollAdminTokenRequestValidate(req, {
admin_token_CustomCheck: token => token !== ''
})
if (err != null) throw new Error(err.message)
return mainHandler.adminManager.PromoteUserToAdmin(ctx.app_id, ctx.app_user_id, req.admin_token)
},
}
}

View file

@ -7,6 +7,7 @@ import * as WizardTypes from "../../../proto/wizard_service/autogenerated/ts/typ
import { MainSettings } from "../main/settings.js"
import Storage from '../storage/index.js'
import { Unlocker } from "../main/unlocker.js"
import { AdminManager } from '../main/adminManager.js';
export type WizardSettings = {
sourceName: string
relayUrl: string
@ -17,31 +18,92 @@ const defaultProviderPub = ""
export class Wizard {
log = getLogger({ component: "wizard" })
settings: MainSettings
unlocker: Unlocker
initialized = false
adminManager: AdminManager
storage: Storage
configQueue: { res: (reload: boolean) => void }[] = []
pendingConfig: WizardSettings | null = null
constructor(mainSettings: MainSettings, storage: Storage) {
awaitingNprofile: { res: (nprofile: string) => void }[] = []
nprofile = ""
relays: string[] = []
constructor(mainSettings: MainSettings, storage: Storage, adminManager: AdminManager) {
this.settings = mainSettings
this.adminManager = adminManager
this.storage = storage
this.log('Starting wizard...')
this.unlocker = new Unlocker(mainSettings, storage)
const wizardServer = NewWizardServer({
WizardState: async () => { return { already_initialized: await this.IsInitialized() } },
WizardState: async () => { return this.WizardState() },
WizardConfig: async ({ req }) => { return this.wizardConfig(req) },
WizardConfirm: async ({ req }) => { return this.wizardConfirm(req) },
GetAdminConnectInfo: async () => { return this.GetAdminConnectInfo() },
GetServiceState: async () => { return this.GetServiceState() }
}, { GuestAuthGuard: async () => "", metricsCallback: () => { }, staticFiles: 'static' })
wizardServer.Listen(mainSettings.servicePort + 1)
}
IsInitialized = () => {
if (this.initialized) {
return true
GetServiceState = async (): Promise<WizardTypes.ServiceStateResponse> => {
const apps = await this.storage.applicationStorage.GetApplications()
const appNamesList = apps.map(app => app.name).join(', ')
return {
admin_npub: this.adminManager.GetAdminNpub(),
http_url: this.settings.serviceUrl,
lnd_state: WizardTypes.LndState.OFFLINE,
nprofile: this.nprofile,
provider_name: appNamesList,
relay_connected: false,
relays: this.relays,
watchdog_ok: false
}
return this.unlocker.IsInitialized()
}
WizardState = async (): Promise<WizardTypes.StateResponse> => {
return {
config_sent: this.pendingConfig !== null,
admin_linked: this.adminManager.GetAdminNpub() !== "",
}
}
IsInitialized = () => {
return !!this.adminManager.GetAdminNpub()
}
WaitUntilConfigured = async (): Promise<boolean> => {
if (this.initialized) {
GetAdminConnectInfo = async (): Promise<WizardTypes.AdminConnectInfoResponse> => {
const nprofile = await this.getNprofile()
const enrolledAdmin = this.adminManager.GetAdminNpub()
if (enrolledAdmin !== "") {
return {
nprofile, connect_info: {
type: WizardTypes.AdminConnectInfoResponse_connect_info_type.ENROLLED_NPUB,
enrolled_npub: enrolledAdmin
}
}
}
const adminEnroll = this.adminManager.ReadAdminEnrollToken()
if (adminEnroll !== "") {
return {
nprofile, connect_info: {
type: WizardTypes.AdminConnectInfoResponse_connect_info_type.ADMIN_TOKEN,
admin_token: adminEnroll
}
}
}
throw new Error("something went wrong initializing admin creds")
}
getNprofile = async (): Promise<string> => {
if (this.nprofile !== "") {
return this.nprofile
}
console.log("waiting for nprofile")
return new Promise((res) => {
this.awaitingNprofile.push({ res })
})
}
AddConnectInfo = (nprofile: string, relays: string[]) => {
this.nprofile = nprofile
this.awaitingNprofile.forEach(q => q.res(nprofile))
this.awaitingNprofile = []
}
Configure = async (): Promise<boolean> => {
if (this.IsInitialized() || this.pendingConfig !== null) {
return false
}
return new Promise((res) => {
@ -49,80 +111,58 @@ export class Wizard {
})
}
wizardConfig = async (req: WizardTypes.ConfigRequest): Promise<WizardTypes.ConfigResponse> => {
wizardConfig = async (req: WizardTypes.ConfigRequest): Promise<void> => {
const err = WizardTypes.ConfigRequestValidate(req, {
source_name_CustomCheck: source => source !== '',
relay_url_CustomCheck: relay => relay !== '',
})
if (err != null) { throw new Error(err.message) }
const res = await this.unlocker.InitInteractive()
if (res.alreadyInitizialized) {
this.initialized = true
this.configQueue.forEach(q => q.res(false))
return { already_initialized: true, confirmation_id: "", seed: [] }
if (this.IsInitialized() || this.pendingConfig !== null) {
throw new Error("already initialized")
}
this.pendingConfig = { sourceName: req.source_name, relayUrl: req.relay_url, automateLiquidity: req.automate_liquidity, pushBackupsToNostr: req.push_backups_to_nostr }
return { already_initialized: false, confirmation_id: res.confirmationId, seed: res.seed }
}
wizardConfirm = async (req: WizardTypes.ConfirmRequest): Promise<WizardTypes.ConfirmResponse> => {
const err = WizardTypes.ConfirmRequestValidate(req, {
confirmation_id_CustomCheck: conf => conf !== '',
})
if (err != null) { throw new Error(err.message) }
const res = await this.unlocker.ConfirmInitInteractive(req.confirmation_id)
if (res?.alreadyInitizialized) {
this.initialized = true
this.configQueue.forEach(q => q.res(false))
return { admin_key: "" }
}
this.initialized = true
this.updateEnvFile()
const pendingConfig = { sourceName: req.source_name, relayUrl: req.relay_url, automateLiquidity: req.automate_liquidity, pushBackupsToNostr: req.push_backups_to_nostr }
this.updateEnvFile(pendingConfig)
this.configQueue.forEach(q => q.res(true))
return { admin_key: process.env.ADMIN_TOKEN || "" }
}
updateEnvFile = () => {
if (!this.pendingConfig) {
this.configQueue = []
return
}
updateEnvFile = (pendingConfig: WizardSettings) => {
let envFileContent: string[] = []
try {
envFileContent = fs.readFileSync('.env', 'utf-8').split('\n')
} catch (err: any) {
if (err.code !== 'ENOENT') {
throw err
}
}
const toMerge: string[] = []
const sourceNameIndex = envFileContent.findIndex(line => line.startsWith('DEFAULT_APP_NAME'))
if (sourceNameIndex === -1) {
toMerge.push(`DEFAULT_APP_NAME=${this.pendingConfig.sourceName}`)
toMerge.push(`DEFAULT_APP_NAME=${pendingConfig.sourceName}`)
} else {
envFileContent[sourceNameIndex] = `DEFAULT_APP_NAME=${this.pendingConfig.sourceName}`
envFileContent[sourceNameIndex] = `DEFAULT_APP_NAME=${pendingConfig.sourceName}`
}
const relayUrlIndex = envFileContent.findIndex(line => line.startsWith('RELAY_URL'))
if (relayUrlIndex === -1) {
toMerge.push(`RELAY_URL=${this.pendingConfig.relayUrl}`)
toMerge.push(`RELAY_URL=${pendingConfig.relayUrl}`)
} else {
envFileContent[relayUrlIndex] = `RELAY_URL=${this.pendingConfig.relayUrl}`
envFileContent[relayUrlIndex] = `RELAY_URL=${pendingConfig.relayUrl}`
}
const automateLiquidityIndex = envFileContent.findIndex(line => line.startsWith('LIQUIDITY_PROVIDER_PUB'))
if (automateLiquidityIndex === -1) {
toMerge.push(`LIQUIDITY_PROVIDER_PUB=${this.pendingConfig.automateLiquidity ? defaultProviderPub : ""}`)
toMerge.push(`LIQUIDITY_PROVIDER_PUB=${pendingConfig.automateLiquidity ? defaultProviderPub : ""}`)
} else {
envFileContent[automateLiquidityIndex] = `LIQUIDITY_PROVIDER_PUB=`
}
const pushBackupsToNostrIndex = envFileContent.findIndex(line => line.startsWith('PUSH_BACKUPS_TO_NOSTR'))
if (pushBackupsToNostrIndex === -1) {
toMerge.push(`PUSH_BACKUPS_TO_NOSTR=${this.pendingConfig.pushBackupsToNostr ? 'true' : 'false'}`)
toMerge.push(`PUSH_BACKUPS_TO_NOSTR=${pendingConfig.pushBackupsToNostr ? 'true' : 'false'}`)
} else {
envFileContent[pushBackupsToNostrIndex] = `PUSH_BACKUPS_TO_NOSTR=${this.pendingConfig.pushBackupsToNostr ? 'true' : 'false'}`
envFileContent[pushBackupsToNostrIndex] = `PUSH_BACKUPS_TO_NOSTR=${pendingConfig.pushBackupsToNostr ? 'true' : 'false'}`
}
const merged = [...envFileContent, ...toMerge].join('\n')
fs.writeFileSync('.env', merged)

View file

@ -93,6 +93,31 @@
<script src="js/backup.js"></script>
<script>
const sendConfig = async () => {
const req = {
source_name: localStorage.getItem("wizard/nodeName"),
relay_url: localStorage.getItem("wizard/relayUrl"),
automate_liquidity: localStorage.getItem("wizard/liquidity") === 'automate',
push_backups_to_nostr: localStorage.getItem("wizard/backup") === 'backup',
}
const res = await fetch("/wizard/config", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(req)
})
if (res.status !== 200) {
document.getElementById('errorText').innerText = "failed to start service"
return
}
const j = await res.json()
if (j.status !== 'OK') {
document.getElementById('errorText').innerText = "failed to start service" + j.reason
return
}
location.href = 'connect.html'
}
document.getElementById("next-button").onclick = (e) => {
const backup = document.getElementById('backup').checked
const manual = document.getElementById('manual-backup').checked
@ -109,7 +134,7 @@
} else {
localStorage.setItem('wizard/backup', 'manual')
}
location.href = 'seed.html'
sendConfig()
}
</script>
</body>

View file

@ -1,14 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Montserrat"
/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat" />
<link rel="stylesheet" href="css/styles.css" />
<link rel="stylesheet" href="css/connect.css" />
<!-- HTML Meta Tags -->
@ -16,14 +14,10 @@
<meta name="description" content="Lightning for Everyone" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<header>
<img
src="img/pub_logo.png"
width="38px"
height="auto"
alt="Lightning Pub logo"
/>
<img src="img/pub_logo.png" width="38px" height="auto" alt="Lightning Pub logo" />
<img src="img/LightningPub.png" height="33px" alt="Lightning Pub logo" />
</header>
@ -41,8 +35,11 @@
<div class="line"></div>
<section class="setup-content">
<div>For dashboard access, use <a href="https://preview.uxpin.com/ae6e6372ab26cd13438d486d4d1ac9d184ec8e82#/pages/164889267" style="color: #2aabe9;" target="_blank">ShockWallet</a> and tap the logo 3 times.</div>
<div style="font-size: 13px; margin-top: 5px;">Scan the QR or Copy-Paste the string to establish the connection.</div>
<div>For dashboard access, use <a
href="https://preview.uxpin.com/ae6e6372ab26cd13438d486d4d1ac9d184ec8e82#/pages/164889267"
style="color: #2aabe9;" target="_blank">ShockWallet</a> and tap the logo 3 times.</div>
<div style="font-size: 13px; margin-top: 5px;">Scan the QR or Copy-Paste the string to establish the connection.
</div>
<div style="display: flex; justify-content: center;">
<div class="qrcode-box" id="codebox">
<div style="font-size: 11px;">
@ -51,14 +48,13 @@
</div>
<div id="qrcode"></div>
<div style="color: #a3a3a3; font-size: 11px;">
<div>npub123abcdefghhhhhhhhhhhhhhh</div>
<div>relay.lightning.pub</div>
<div id="connectString"></div>
</div>
</div>
</div>
</section>
</main>
<p class="errorText" style="color:red"></p>
<footer>
<div class="footer-text">
<div>By proceeding you acknowledge that this is</div>
@ -74,7 +70,47 @@
<script src="https://cdn.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js"></script>
<script src="js/script.js"></script>
<script src="js/connect.js"></script>
<script>
const fetchInfo = async () => {
console.log("fewtching...")
const res = await fetch("/wizard/admin_connect_info")
console.log(res)
if (res.status !== 200) {
document.getElementById('errorText').innerText = "failed to get connection info"
return
}
const j = await res.json()
console.log(j)
if (j.status !== 'OK') {
document.getElementById('errorText').innerText = "failed to get connection info" + j.reason
return
}
if (j.connect_info.enrolled_npub) {
location.href = 'status.html'
} else {
const connectString = j.nprofile + ":" + j.connect_info.admin_token
console.log({ connectString })
const qrElement = document.getElementById("qrcode")
qrElement.onclick = () => {
document.navigator.clipboard.writeText(connectString)
}
const qrcode = new QRCode(qrElement, {
text: connectString,
colorDark: "#000000",
colorLight: "#ffffff",
width: 157,
height: 157,
// correctLevel : QRCode.CorrectLevel.H
});
document.getElementById('connectString').innerHTML = connectString
}
}
try {
fetchInfo()
} catch (e) { console.log({ e }) }
</script>
</body>
</html>

View file

@ -96,9 +96,10 @@
fetch("/wizard/state").then((res) => {
if (res.status === 200) {
res.json().then((data) => {
if (data.already_initialized) {
if (data.admin_linked) {
location.href = 'status.html'
console.log("already init")
} else if (data.config_sent) {
location.href = 'connect.html'
} else {
console.log("ready to initialize")
}

View file

@ -69,60 +69,6 @@
</footer>
<script src="js/seed.js"></script>
<script>
let latestConfirmationId = ""
document.getElementById("next-button").onclick = (e) => {
if (!latestConfirmationId) {
return
}
fetch("/wizard/confirm", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
confirmation_id: latestConfirmationId,
})
}).then((res) => {
if (res.status === 200) {
location.href = 'connect.html'
}
})
}
fetch("/wizard/config", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
source_name: localStorage.getItem("wizard/nodeName"),
relay_url: localStorage.getItem("wizard/relayUrl"),
automate_liquidity: localStorage.getItem("wizard/liquidity") === 'automate',
push_backups_to_nostr: localStorage.getItem("wizard/backup") === 'backup',
})
}).then((res) => {
if (res.status === 200) {
res.json().then((data) => {
if (data.already_initialized) {
location.href = 'status.html'
return
}
const seedContainer = document.getElementById("seed-box-container")
latestConfirmationId = data.confirmation_id
console.log(latestConfirmationId)
data.seed.forEach((word, index) => {
const seedBox = document.createElement('div')
seedBox.classList.add('seed-box')
seedBox.innerHTML = `
<span>${index + 1}</span>
<span>${word}</span>
`
seedContainer.appendChild(seedBox)
})
})
}
})
</script>
</body>
</html>

View file

@ -30,6 +30,7 @@
<div class="line" style="width: 100%;"></div>
<section class="node-status">
<p id="errorText" style="color:red"></p>
<div>
<div class="status-element" style="margin-top: 15px;">
<div style="text-align: left;">Public Node Name:</div>
@ -69,8 +70,8 @@
</div>
<div class="status-element" style="margin-top: 15px;">
<div>Administrator:</div>
<div>
npub12334556677889990
<div id="adminNpub" style="line-break: anywhere;">
Loading...
</div>
</div>
</div>
@ -99,14 +100,14 @@
<div style="margin-top: 40px;">
<div class="status-element">
<div>Relay Status:</div>
<div>
<span class="green-dot">&#9679;</span> Connected
<div id="relayStatus">
<span class="yellow-dot">&#9679;</span> Loading...
</div>
</div>
<div class="status-element">
<div>Lightning Status:</div>
<div>
<span class="yellow-dot">&#9679;</span> Syncing
<div id="lndStatus">
<span class="yellow-dot">&#9679;</span> Loading...
</div>
</div>
<div class="status-element">
@ -118,15 +119,15 @@
</button>
</div>
</div>
<div>
<span class="green-dot">&#9679;</span> No Alarms
<div id="watchdog-status">
<span class="green-dot">&#9679;</span> Loading...
</div>
</div>
</div>
<div style="margin-top: 20px;">
<div style="font-size: 13px; text-align: left;">Guest Invitation Link:</div>
<a href="https://my.shockwallet.app/invite/nprofile12345678899988" target="_blank" style="font-size: 11px;"
class="invite-link">
<a href="https://my.shockwallet.app/invite/nprofile12345678899988" target="_blank"
style="font-size: 11px;line-break: anywhere;" id="inviteLinkHttp" class="invite-link">
https://my.shockwallet.app/invite/nprofile12345678899988
</div>
</div>
@ -140,6 +141,34 @@
</footer>
<script src="js/status.js"></script>
<script>
const fetchInfo = async () => {
console.log("fewtching...")
const res = await fetch("/wizard/service_state")
console.log(res)
if (res.status !== 200) {
document.getElementById('errorText').innerText = "failed to get state info"
return
}
const j = await res.json()
console.log(j)
if (j.status !== 'OK') {
document.getElementById('errorText').innerText = "failed to get state info" + j.reason
return
}
document.getElementById("show-nodey-text").innerHTML = j.provider_name
document.getElementById("show-nostr-text").innerHTML = j.relays[0]
document.getElementById("adminNpub").innerText = j.admin_npub
document.getElementById("relayStatus").innerHTML = `<span class="${j.relay_connected ? 'green-dot' : 'red-dot'}">&#9679;</span> ${j.relay_connected ? 'Connected' : 'Disconnected'}`
document.getElementById("lndStatus").innerHTML = `<span class="${j.lnd_state === 'ONLINE' ? 'green-dot' : 'red-dot'}">&#9679;</span> ${j.lnd_state}`
document.getElementById("watchdog-status").innerHTML = `<span class="${j.watchdog_ok ? 'green-dot' : 'red-dot'}">&#9679;</span> ${j.watchdog_ok ? 'No Alerts' : 'ALERT!!'}`
document.getElementById("inviteLinkHttp").href = `https://my.shockwallet.app/invite/${j.nprofile}`
document.getElementById("inviteLinkHttp").innerHTML = `https://my.shockwallet.app/invite/${j.nprofile}`
}
try {
fetchInfo()
} catch (e) { console.log({ e }) }
</script>
</body>
</html>