diff --git a/proto/autogenerated/client.md b/proto/autogenerated/client.md index 3e3eaca3..cbe44a34 100644 --- a/proto/autogenerated/client.md +++ b/proto/autogenerated/client.md @@ -475,6 +475,13 @@ The nostr server will send back a message response, and inside the body there wi - This methods has an __empty__ __request__ body - output: [MigrationUpdate](#MigrationUpdate) +- GetNPubLinkingState + - auth type: __App__ + - http method: __post__ + - http route: __/api/app/user/npub/token__ + - input: [GetNPubLinking](#GetNPubLinking) + - output: [NPubLinking](#NPubLinking) + - GetPaymentState - auth type: __User__ - http method: __post__ @@ -862,6 +869,9 @@ The nostr server will send back a message response, and inside the body there wi ### GetInviteTokenStateResponse - __used__: _boolean_ +### GetNPubLinking + - __user_identifier__: _string_ + ### GetPaymentStateRequest - __invoice__: _string_ @@ -967,6 +977,9 @@ The nostr server will send back a message response, and inside the body there wi - __closure__: _[ClosureMigration](#ClosureMigration)_ *this field is optional - __relays__: _[RelaysMigration](#RelaysMigration)_ *this field is optional +### NPubLinking + - __state__: _[NPubLinking_state](#NPubLinking_state)_ + ### NewAddressRequest - __addressType__: _[AddressType](#AddressType)_ diff --git a/proto/autogenerated/go/http_client.go b/proto/autogenerated/go/http_client.go index 446d5e7a..229ef34a 100644 --- a/proto/autogenerated/go/http_client.go +++ b/proto/autogenerated/go/http_client.go @@ -85,6 +85,7 @@ type Client struct { GetLnurlWithdrawInfo func(query GetLnurlWithdrawInfo_Query) (*LnurlWithdrawInfoResponse, error) GetLnurlWithdrawLink func() (*LnurlLinkResponse, error) GetMigrationUpdate func() (*MigrationUpdate, error) + GetNPubLinkingState func(req GetNPubLinking) (*NPubLinking, error) GetPaymentState func(req GetPaymentStateRequest) (*PaymentState, error) GetSeed func() (*LndSeed, error) GetUsageMetrics func() (*UsageMetrics, error) @@ -834,6 +835,35 @@ func NewClient(params ClientParams) *Client { return &res, nil }, // server streaming method: GetMigrationUpdate not implemented + GetNPubLinkingState: func(req GetNPubLinking) (*NPubLinking, error) { + auth, err := params.RetrieveAppAuth() + if err != nil { + return nil, err + } + finalRoute := "/api/app/user/npub/token" + body, err := json.Marshal(req) + if err != nil { + return nil, err + } + resBody, err := doPostRequest(params.BaseURL+finalRoute, body, auth) + if err != nil { + return nil, err + } + result := ResultError{} + err = json.Unmarshal(resBody, &result) + if err != nil { + return nil, err + } + if result.Status == "ERROR" { + return nil, fmt.Errorf(result.Reason) + } + res := NPubLinking{} + err = json.Unmarshal(resBody, &res) + if err != nil { + return nil, err + } + return &res, nil + }, GetPaymentState: func(req GetPaymentStateRequest) (*PaymentState, error) { auth, err := params.RetrieveUserAuth() if err != nil { diff --git a/proto/autogenerated/go/types.go b/proto/autogenerated/go/types.go index 1f29f6b5..0031db57 100644 --- a/proto/autogenerated/go/types.go +++ b/proto/autogenerated/go/types.go @@ -228,6 +228,9 @@ type GetInviteTokenStateRequest struct { type GetInviteTokenStateResponse struct { Used bool `json:"used"` } +type GetNPubLinking struct { + User_identifier string `json:"user_identifier"` +} type GetPaymentStateRequest struct { Invoice string `json:"invoice"` } @@ -333,6 +336,9 @@ type MigrationUpdate struct { Closure *ClosureMigration `json:"closure"` Relays *RelaysMigration `json:"relays"` } +type NPubLinking struct { + State *NPubLinking_state `json:"state"` +} type NewAddressRequest struct { Addresstype AddressType `json:"addressType"` } @@ -542,3 +548,17 @@ type LiveDebitRequest_debit struct { Full_access *Empty `json:"full_access"` Invoice *string `json:"invoice"` } +type NPubLinking_state_type string + +const ( + LINKED_NPUB NPubLinking_state_type = "linked_npub" + LINKING_TOKEN NPubLinking_state_type = "linking_token" + UNLINKED NPubLinking_state_type = "unlinked" +) + +type NPubLinking_state struct { + Type NPubLinking_state_type `json:"type"` + Linked_npub *string `json:"linked_npub"` + Linking_token *string `json:"linking_token"` + Unlinked *Empty `json:"unlinked"` +} diff --git a/proto/autogenerated/ts/express_server.ts b/proto/autogenerated/ts/express_server.ts index 997d3719..86933f07 100644 --- a/proto/autogenerated/ts/express_server.ts +++ b/proto/autogenerated/ts/express_server.ts @@ -878,6 +878,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.GetNPubLinkingState) throw new Error('method: GetNPubLinkingState is not implemented') + app.post('/api/app/user/npub/token', async (req, res) => { + const info: Types.RequestInfo = { rpcName: 'GetNPubLinkingState', 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.GetNPubLinkingState) throw new Error('method: GetNPubLinkingState is not implemented') + const authContext = await opts.AppAuthGuard(req.headers['authorization']) + authCtx = authContext + stats.guard = process.hrtime.bigint() + const request = req.body + const error = Types.GetNPubLinkingValidate(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 + const response = await methods.GetNPubLinkingState({rpcName:'GetNPubLinkingState', ctx:authContext , req: request}) + 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.GetPaymentState) throw new Error('method: GetPaymentState is not implemented') app.post('/api/user/payment/state', async (req, res) => { const info: Types.RequestInfo = { rpcName: 'GetPaymentState', batch: false, nostr: false, batchSize: 0} diff --git a/proto/autogenerated/ts/http_client.ts b/proto/autogenerated/ts/http_client.ts index 56f06543..e0fe4804 100644 --- a/proto/autogenerated/ts/http_client.ts +++ b/proto/autogenerated/ts/http_client.ts @@ -388,6 +388,20 @@ export default (params: ClientParams) => ({ return { status: 'ERROR', reason: 'invalid response' } }, GetMigrationUpdate: async (cb: (v:ResultError | ({ status: 'OK' }& Types.MigrationUpdate)) => void): Promise => { throw new Error('http streams are not supported')}, + GetNPubLinkingState: async (request: Types.GetNPubLinking): Promise => { + const auth = await params.retrieveAppAuth() + if (auth === null) throw new Error('retrieveAppAuth() returned null') + let finalRoute = '/api/app/user/npub/token' + 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.NPubLinkingValidate(result) + if (error === null) { return { status: 'OK', ...result } } else return { status: 'ERROR', reason: error.message } + } + return { status: 'ERROR', reason: 'invalid response' } + }, GetPaymentState: async (request: Types.GetPaymentStateRequest): Promise => { const auth = await params.retrieveUserAuth() if (auth === null) throw new Error('retrieveUserAuth() returned null') diff --git a/proto/autogenerated/ts/types.ts b/proto/autogenerated/ts/types.ts index a142deca..8b624e32 100644 --- a/proto/autogenerated/ts/types.ts +++ b/proto/autogenerated/ts/types.ts @@ -12,8 +12,8 @@ export type AdminMethodOutputs = AddApp_Output | AuthApp_Output | BanUser_Output export type AppContext = { app_id: string } -export type AppMethodInputs = AddAppInvoice_Input | AddAppUser_Input | AddAppUserInvoice_Input | GetApp_Input | GetAppUser_Input | GetAppUserLNURLInfo_Input | PayAppUserInvoice_Input | RequestNPubLinkingToken_Input | ResetNPubLinkingToken_Input | SendAppUserToAppPayment_Input | SendAppUserToAppUserPayment_Input | SetMockAppBalance_Input | SetMockAppUserBalance_Input -export type AppMethodOutputs = AddAppInvoice_Output | AddAppUser_Output | AddAppUserInvoice_Output | GetApp_Output | GetAppUser_Output | GetAppUserLNURLInfo_Output | PayAppUserInvoice_Output | RequestNPubLinkingToken_Output | ResetNPubLinkingToken_Output | SendAppUserToAppPayment_Output | SendAppUserToAppUserPayment_Output | SetMockAppBalance_Output | SetMockAppUserBalance_Output +export type AppMethodInputs = AddAppInvoice_Input | AddAppUser_Input | AddAppUserInvoice_Input | GetApp_Input | GetAppUser_Input | GetAppUserLNURLInfo_Input | GetNPubLinkingState_Input | PayAppUserInvoice_Input | RequestNPubLinkingToken_Input | ResetNPubLinkingToken_Input | SendAppUserToAppPayment_Input | SendAppUserToAppUserPayment_Input | SetMockAppBalance_Input | SetMockAppUserBalance_Input +export type AppMethodOutputs = AddAppInvoice_Output | AddAppUser_Output | AddAppUserInvoice_Output | GetApp_Output | GetAppUser_Output | GetAppUserLNURLInfo_Output | GetNPubLinkingState_Output | PayAppUserInvoice_Output | RequestNPubLinkingToken_Output | ResetNPubLinkingToken_Output | SendAppUserToAppPayment_Output | SendAppUserToAppUserPayment_Output | SetMockAppBalance_Output | SetMockAppUserBalance_Output export type GuestContext = { } export type GuestMethodInputs = EncryptionExchange_Input | GetLnurlPayInfo_Input | GetLnurlWithdrawInfo_Input | HandleLnurlAddress_Input | HandleLnurlPay_Input | HandleLnurlWithdraw_Input | Health_Input | SetMockInvoiceAsPaid_Input @@ -137,6 +137,9 @@ export type GetLnurlWithdrawLink_Output = ResultError | ({ status: 'OK' } & Lnur export type GetMigrationUpdate_Input = {rpcName:'GetMigrationUpdate', cb:(res: MigrationUpdate, err:Error|null)=> void} export type GetMigrationUpdate_Output = ResultError | { status: 'OK' } +export type GetNPubLinkingState_Input = {rpcName:'GetNPubLinkingState', req: GetNPubLinking} +export type GetNPubLinkingState_Output = ResultError | ({ status: 'OK' } & NPubLinking) + export type GetPaymentState_Input = {rpcName:'GetPaymentState', req: GetPaymentStateRequest} export type GetPaymentState_Output = ResultError | ({ status: 'OK' } & PaymentState) @@ -277,6 +280,7 @@ export type ServerMethods = { GetLnurlWithdrawInfo?: (req: GetLnurlWithdrawInfo_Input & {ctx: GuestContext }) => Promise GetLnurlWithdrawLink?: (req: GetLnurlWithdrawLink_Input & {ctx: UserContext }) => Promise GetMigrationUpdate?: (req: GetMigrationUpdate_Input & {ctx: UserContext }) => Promise + GetNPubLinkingState?: (req: GetNPubLinkingState_Input & {ctx: AppContext }) => Promise GetPaymentState?: (req: GetPaymentState_Input & {ctx: UserContext }) => Promise GetSeed?: (req: GetSeed_Input & {ctx: AdminContext }) => Promise GetUsageMetrics?: (req: GetUsageMetrics_Input & {ctx: MetricsContext }) => Promise @@ -1263,6 +1267,24 @@ export const GetInviteTokenStateResponseValidate = (o?: GetInviteTokenStateRespo return null } +export type GetNPubLinking = { + user_identifier: string +} +export const GetNPubLinkingOptionalFields: [] = [] +export type GetNPubLinkingOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + user_identifier_CustomCheck?: (v: string) => boolean +} +export const GetNPubLinkingValidate = (o?: GetNPubLinking, opts: GetNPubLinkingOptions = {}, path: string = 'GetNPubLinking::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.user_identifier !== 'string') return new Error(`${path}.user_identifier: is not a string`) + if (opts.user_identifier_CustomCheck && !opts.user_identifier_CustomCheck(o.user_identifier)) return new Error(`${path}.user_identifier: custom check failed`) + + return null +} + export type GetPaymentStateRequest = { invoice: string } @@ -1910,6 +1932,25 @@ export const MigrationUpdateValidate = (o?: MigrationUpdate, opts: MigrationUpda return null } +export type NPubLinking = { + state: NPubLinking_state +} +export const NPubLinkingOptionalFields: [] = [] +export type NPubLinkingOptions = OptionsBaseMessage & { + checkOptionalsAreSet?: [] + state_Options?: NPubLinking_stateOptions +} +export const NPubLinkingValidate = (o?: NPubLinking, opts: NPubLinkingOptions = {}, path: string = 'NPubLinking::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') + + const stateErr = NPubLinking_stateValidate(o.state, opts.state_Options, `${path}.state`) + if (stateErr !== null) return stateErr + + + return null +} + export type NewAddressRequest = { addressType: AddressType } @@ -2982,6 +3023,50 @@ export const LiveDebitRequest_debitValidate = (o?: LiveDebitRequest_debit, opts: if (typeof o.invoice !== 'string') return new Error(`${path}.invoice: is not a string`) if (opts.invoice_CustomCheck && !opts.invoice_CustomCheck(o.invoice)) return new Error(`${path}.invoice: custom check failed`) + break + default: + return new Error(path + ': unknown type '+ stringType) + } + return null +} +export enum NPubLinking_state_type { + LINKED_NPUB = 'linked_npub', + LINKING_TOKEN = 'linking_token', + UNLINKED = 'unlinked', +} +export const enumCheckNPubLinking_state_type = (e?: NPubLinking_state_type): boolean => { + for (const v in NPubLinking_state_type) if (e === v) return true + return false +} +export type NPubLinking_state = + {type:NPubLinking_state_type.LINKED_NPUB, linked_npub:string}| + {type:NPubLinking_state_type.LINKING_TOKEN, linking_token:string}| + {type:NPubLinking_state_type.UNLINKED, unlinked:Empty} + +export type NPubLinking_stateOptions = { + linked_npub_CustomCheck?: (v: string) => boolean + linking_token_CustomCheck?: (v: string) => boolean + unlinked_Options?: EmptyOptions +} +export const NPubLinking_stateValidate = (o?: NPubLinking_state, opts:NPubLinking_stateOptions = {}, path: string = 'NPubLinking_state::root.'): Error | null => { + if (typeof o !== 'object' || o === null) return new Error(path + ': object is not an instance of an object or is null') + const stringType: string = o.type + switch (o.type) { + case NPubLinking_state_type.LINKED_NPUB: + if (typeof o.linked_npub !== 'string') return new Error(`${path}.linked_npub: is not a string`) + if (opts.linked_npub_CustomCheck && !opts.linked_npub_CustomCheck(o.linked_npub)) return new Error(`${path}.linked_npub: custom check failed`) + + break + case NPubLinking_state_type.LINKING_TOKEN: + if (typeof o.linking_token !== 'string') return new Error(`${path}.linking_token: is not a string`) + if (opts.linking_token_CustomCheck && !opts.linking_token_CustomCheck(o.linking_token)) return new Error(`${path}.linking_token: custom check failed`) + + break + case NPubLinking_state_type.UNLINKED: + const unlinkedErr = EmptyValidate(o.unlinked, opts.unlinked_Options, `${path}.unlinked`) + if (unlinkedErr !== null) return unlinkedErr + + break default: return new Error(path + ': unknown type '+ stringType) diff --git a/proto/service/methods.proto b/proto/service/methods.proto index c86346b1..ed6b0649 100644 --- a/proto/service/methods.proto +++ b/proto/service/methods.proto @@ -318,14 +318,19 @@ service LightningPub { option (http_method) = "post"; option (http_route) = "/api/app/mock/blance/set"; } + rpc GetNPubLinkingState(structs.GetNPubLinking) returns (structs.NPubLinking) { + option (auth_type) = "App"; + option (http_method) = "post"; + option (http_route) = "/api/app/user/npub/token"; + } rpc RequestNPubLinkingToken(structs.RequestNPubLinkingTokenRequest) returns (structs.RequestNPubLinkingTokenResponse) { option (auth_type) = "App"; - option(http_method) = "post"; + option (http_method) = "post"; option (http_route) = "/api/app/user/npub/token"; } rpc ResetNPubLinkingToken(structs.RequestNPubLinkingTokenRequest) returns (structs.RequestNPubLinkingTokenResponse) { option (auth_type) = "App"; - option(http_method) = "post"; + option (http_method) = "post"; option (http_route) = "/api/app/user/npub/token/reset"; } // diff --git a/proto/service/structs.proto b/proto/service/structs.proto index 1d320f9e..d885f22f 100644 --- a/proto/service/structs.proto +++ b/proto/service/structs.proto @@ -441,6 +441,18 @@ message RelaysMigration { repeated string relays = 1; } +message GetNPubLinking { + string user_identifier = 1; +} + +message NPubLinking { + oneof state { + Empty unlinked = 1; + string linked_npub = 2; + string linking_token = 3; + } +} + message RequestNPubLinkingTokenRequest { string user_identifier = 1; diff --git a/src/services/main/applicationManager.ts b/src/services/main/applicationManager.ts index 97fa8448..651a428b 100644 --- a/src/services/main/applicationManager.ts +++ b/src/services/main/applicationManager.ts @@ -19,6 +19,7 @@ type NsecLinkingData = { expiry: number } export default class { + storage: Storage settings: MainSettings paymentManager: PaymentManager @@ -242,6 +243,20 @@ export default class { const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier) return this.paymentManager.GetLnurlPayInfoFromUser(user.user.user_id, app, { baseUrl: req.base_url_override }) } + + async GetNPubLinkingState(app_id: string, req: Types.GetNPubLinking): Promise { + const app = await this.storage.applicationStorage.GetApplication(app_id); + const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier); + const linking = Object.entries(this.nPubLinkingTokens).find(([_, data]) => data.serialId === user.serial_id) + if (linking) { + return { state: { type: Types.NPubLinking_state_type.LINKING_TOKEN, linking_token: linking[0] } } + } + if (user.nostr_public_key) { + return { state: { type: Types.NPubLinking_state_type.LINKED_NPUB, linked_npub: user.nostr_public_key } } + } + return { state: { type: Types.NPubLinking_state_type.UNLINKED, unlinked: {} } } + } + async RequestNPubLinkingToken(appId: string, req: Types.RequestNPubLinkingTokenRequest, reset: boolean): Promise { const app = await this.storage.applicationStorage.GetApplication(appId); const user = await this.storage.applicationStorage.GetApplicationUser(app, req.user_identifier); diff --git a/src/services/serverMethods/index.ts b/src/services/serverMethods/index.ts index b116406d..0e2465aa 100644 --- a/src/services/serverMethods/index.ts +++ b/src/services/serverMethods/index.ts @@ -217,6 +217,13 @@ export default (mainHandler: Main): Types.ServerMethods => { }, GetMigrationUpdate: async ({ ctx, cb }) => { }, + GetNPubLinkingState: async ({ ctx, req }) => { + const err = Types.GetNPubLinkingValidate(req, { + user_identifier_CustomCheck: userIdentifier => userIdentifier !== '', + }) + if (err != null) throw new Error(err.message) + return mainHandler.applicationManager.GetNPubLinkingState(ctx.app_id, req) + }, RequestNPubLinkingToken: async ({ ctx, req }) => { const err = Types.RequestNPubLinkingTokenRequestValidate(req, { user_identifier_CustomCheck: userIdentifier => userIdentifier !== '',